Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pac
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tools
pac
Commits
15d08d44
Commit
15d08d44
authored
Oct 22, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
docs: archive operational records and architecture discussions
parent
c1e09831
Pipeline
#3861
failed in 0 seconds
Changes
15
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
1189 additions
and
0 deletions
+1189
-0
docs/README.md
+2
-0
docs/_archive/2026-10-operations-and-design/2026-10-14-push-latency-local.md
+22
-0
docs/_archive/2026-10-operations-and-design/2026-10-14-return-visit-push-fix-test.md
+37
-0
docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review-document.html
+194
-0
docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review.html
+214
-0
docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review.md
+146
-0
docs/_archive/2026-10-operations-and-design/2026-10-18-business-date-shanghai-test-validation.md
+40
-0
docs/_archive/2026-10-operations-and-design/2026-10-18-jvs-complaint-ingestion-prod-validation.md
+68
-0
docs/_archive/2026-10-operations-and-design/2026-10-18-jvs-complaint-ingestion-test-validation.md
+77
-0
docs/_archive/2026-10-operations-and-design/2026-10-19-meyer-friday-pac-imaging-review-document.html
+93
-0
docs/_archive/2026-10-operations-and-design/2026-10-19-meyer-friday-pac-imaging-review.md
+85
-0
docs/_archive/2026-10-operations-and-design/2026-10-20-data-warehouse-and-pac-positioning.md
+84
-0
docs/_archive/2026-10-operations-and-design/README.md
+37
-0
docs/_archive/2026-10-operations-and-design/manifest.json
+83
-0
docs/_archive/README.md
+7
-0
No files found.
docs/README.md
View file @
15d08d44
...
...
@@ -22,6 +22,8 @@ PAC 客户召回与 FridayAI 的正式产品、原理和接口文档统一维护
临时规划、旧流程、环境快照、验收报告和重复销售稿移入
[
_archive/
](
_archive/README.md
)
,保留原文件名、引用与移动清单。
2026 年 10 月新增归档:
[
美亚与患者事实开发记录
](
_archive/2026-10-meyer-and-patient-facts/README.md
)
、
[
运维验收与架构讨论
](
_archive/2026-10-operations-and-design/README.md
)
。
设计准则、行为埋点和架构决策已全部归档至
[
_archive/design/
](
_archive/design/
)
与
[
_archive/adr/
](
_archive/adr/
)
。
Claude Code 的编号资料见
[
FridayAI 开发档案
](
_archive/friday-ai/docs/README.md
)
。未结事项见
[
跟进清单
](
qa/follow-ups.md
)
,归档不表示解决。环境现状需要重新查询;当前行为以代码和正式文档为准。
docs/
qa
/2026-10-14-push-latency-local.md
→
docs/
_archive/2026-10-operations-and-design
/2026-10-14-push-latency-local.md
View file @
15d08d44
# Push 关键路径优化与排障(本地)
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。
分支
`codex/push-latency-fix`
,基于 main
`99fb2648cacdafcd0f26ec8540d04c8465ecbb87`
。独立 worktree,主目录及正在运行的本地服务未变。已提交并部署测试;生产未改动。
## 最终边界
...
...
@@ -60,3 +62,23 @@
-
历史字段审计在后台完成 20ms,不阻塞响应。同期主档请求 195ms。健康 JSON status=ok。
-
本条真实预约没有慢环节,确认业务落库后毫秒级返回;它不能证明此前分钟级超时的具体根因,也不是宿主建预约到消费响应的端到端计时。
-
已获得真实预约样本,暂停本轮定时跟踪。生产未修改。
## 生产发布准备(2026-10-14)
-
用户验收测试并授权合 main、部署生产;检查发现 4 个召回主查询及其并行进程运行中,用户进一步明确允许中断本轮后部署。
-
main 快进到
`584bd3dd34c2ed20f313d31c476b7f14220f4a88`
;相比测试的业务代码相同,增加测试验收记录。没有将 test 的其他提交合入 main。
-
生产原版本
`99fb264`
,可用内存约 11GiB、/data 可用134GiB,旧服务健康。队列为空但召回SQL活跃,不能据队列断言空闲。
-
有一条13:03:41开始的病历 push 仍标 running,早于原容器13:03:42启动;未将其状态改成功/失败。
-
回退目录
`/data/backups/pac-release/584bd3d-20261014`
:原源码SHA、PAC旧镜像及回退标签、私密配置副本、Compose校验值、迁移元数据、AI/ingress原镜像和启动时间、构建/部署日志与退出标记。配置/镜像备份不是业务数据备份。
-
生产镜像构建退出0。使用 COMPOSE_MANAGED=1、固定SHA、标准发布脚本 --no-pull,只发布PAC;本次唯一数据库变更为并发索引,不重放数据。
## 生产发布验收
-
发布时间北京时间 19:02:51—19:10:29,固定 main@584bd3d,标准部署脚本退出0。
-
并发索引扫描/校验完成后等待旧快照;按用户本轮明确授权,仅取消了4条已核对SQL形态的旧召回主查询(1540/1556/1621/1554),随后迁移成功。没有取消其他查询或手工标记业务任务成功。
-
索引迁移19:10:12完成,ready/valid均true,索引350MB;Prisma无pending。
-
实际运行镜像与构建产物一致:PAC service
`271291ef75ee`
,web
`b88c9fbdf169`
,docs
`2fb22de20fd0`
。
-
公网 https://pac.friday.tech/health 返回JSON status=ok;公网/plans为200;loopback及私网health/web通过;docs
`/docs/integration/channel-push`
本机和私网200。
-
service/web env逐字未变,Compose校验值一致;FridayAI service/web及ingress镜像和启动时间均与发布前一致。
-
首次验收期间生产Friday尚无新push,未声称生产真实推送性能已验证;测试真实预约167ms的结果见上节。
-
主目录原有未跟踪AGENTS.md/output保持原样,本地服务未操作。
docs/_archive/2026-10-operations-and-design/2026-10-14-return-visit-push-fix-test.md
0 → 100644
View file @
15d08d44
# 测试回访推送修复验收
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。
-
功能提交:dca8663;test 合并并部署:a627d29cbf75321c88f2a58b89ea174119ac62ff。
-
仅测试环境;未合入 main、未部署生产。主目录已有文件未改。
-
根因:回访/患者关系仍加载整个租户患者索引,138772 条查询结果通过 push(...rows) 展开触发 Maximum call stack size exceeded。
-
修复:两条链路按本批引用患者、tenant、sourceUnit 查询(关系包含本人和关系人),每块1000个ID;逐条合并结果,索引查询禁止省略引用范围。
-
同类检查:摄入模块其余查询展开为固定50条;CLI患者查询已有20000条分块;未发现另一处相同的无界租户查询展开。
-
本地:5 suites / 46 tests 通过;service 两组 type-check 通过。新工作区先构建 types/utils/host-connector 和 Prisma client。
-
部署退出0,迁移无pending,运行镜像校验通过,/health JSON status=ok。
-
回退资料:测试机 /root/pac-ops/return-visit-fix-20261014,旧service/web镜像已打回退标签。
## 单条原文重放
-
原失败 syncLog:5e6bcd20-2edc-4847-8d31-f54f348ef829。
-
原证据:afc4d32c-721c-4493-9443-6c494f272a30。
-
原文保持不变,经 HMAC 签名调用正常 /pac/v1/push/rows;严格限定 Friday、customer_return_visit、1条、回访267076、患者700687。
-
成功 syncLog:2edf876a-adad-419f-83cf-534107223b07。
-
HTTP200 / code0 / hasFailures=false / failed=0 / factsFailed=0 / stubs=0。
-
请求396ms;lookup-transform9ms、upsert34ms、touched8ms。
-
该回访已落 patient_return_visits:患者与祥云小镇诊所匹配,已回访,执行人5992。
-
直接调用部署版本 AgentRosterService 验证:5992在该诊所名册,recentVisits=1,inRoster=true。
-
回访为独立资源,正常 transactionsWritten=0、personaEnqueued=0;未修改事实或重算召回。
-
未重放其他回访,未读写5i5ya业务数据。
## 生产发布(用户测试确认后授权)
-
main 合并版本:fa09e5ff75aa4e9414e679166b492ad98a533375。
-
一并发布 main 原有未部署改动:FridayAI侧栏导航保持会话和诊所状态(a33528b,合并efc2f64)。该修复专项9项测试及ai-web类型检查通过。
-
发布前:同步日志无running,5个业务队列active/waiting/delayed均0;召回筛选查询pid12383/12411/12412已运行约33分钟,用户明确允许中断本轮后发布。
-
PAC和FridayAI串行发布均exit0,运行镜像校验通过,无待执行迁移;PAC /health、AI /health/ready、私网页面与AI到PAC连通性通过。
-
发布后旧召回查询已不在pg_stat_activity;本轮未手动重算召回。
-
截至2026-10-14 23:25(上海)未看到发布后新push,不能将测试重放结果当作生产真实回访验收;已确认生产编译代码包含scopedPatientIndexLoader。
-
检查启动后日志未见ERROR/栈溢出;未向生产重放测试回访。
-
回退目录:/data/backups/pac-release/return-visit-20261014(旧镜像标签、环境配置、compose、发布日志和退出码)。
-
main主目录未提交文件及本地服务未修改;QA记录保留在隔离工作区,尚未提交。
docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review-document.html
0 → 100644
View file @
15d08d44
<!doctype html>
<html
lang=
"zh-CN"
><head><meta
charset=
"utf-8"
><meta
name=
"viewport"
content=
"width=device-width,initial-scale=1"
><meta
name=
"color-scheme"
content=
"light dark"
><meta
name=
"description"
content=
"美亚、FridaySaaS 与 PAC 的影像及分析数据方案,供内部评审。"
><title>
影像与分析数据方案设计 · 美亚 × FridaySaaS × PAC
</title><style>
:root
{
color-scheme
:
light
;
--ground
:
#f4f6fb
;
--surface
:
#fff
;
--sunken
:
#eaeef6
;
--ink
:
#0f1b33
;
--ink-2
:
#44506a
;
--ink-3
:
#6f7a91
;
--brand
:
#0032a0
;
--brand-soft
:
#e3eafa
;
--rule
:
#dde3ee
;
--note
:
#f5f7fc
;
--warn-bg
:
#fff9ed
;
--warn-rule
:
#d6ae63
;
--serif
:
"Noto Serif SC"
,
"Songti SC"
,
"STSong"
,
serif
;
--sans
:
"Noto Sans SC"
,
"PingFang SC"
,
"Microsoft YaHei"
,
system-ui
,
sans-serif
;
--mono
:
"IBM Plex Mono"
,
ui-monospace
,
"SFMono-Regular"
,
Menlo
,
monospace
}
:root
[
data-theme
=
dark
]
{
color-scheme
:
dark
;
--ground
:
#0a1020
;
--surface
:
#121a2d
;
--sunken
:
#1a2440
;
--ink
:
#e7ecf6
;
--ink-2
:
#b3bdd2
;
--ink-3
:
#8994ab
;
--brand
:
#86a7ff
;
--brand-soft
:
#1d2b52
;
--rule
:
#2a3550
;
--note
:
#172037
;
--warn-bg
:
#292518
;
--warn-rule
:
#a38a55
}
@media
(
prefers-color-scheme
:
dark
){
:root:not
([
data-theme
=
light
])
{
color-scheme
:
dark
;
--ground
:
#0a1020
;
--surface
:
#121a2d
;
--sunken
:
#1a2440
;
--ink
:
#e7ecf6
;
--ink-2
:
#b3bdd2
;
--ink-3
:
#8994ab
;
--brand
:
#86a7ff
;
--brand-soft
:
#1d2b52
;
--rule
:
#2a3550
;
--note
:
#172037
;
--warn-bg
:
#292518
;
--warn-rule
:
#a38a55
}}
*
{
box-sizing
:
border-box
}
html
{
scroll-behavior
:
smooth
}
body
{
margin
:
0
;
background
:
var
(
--ground
);
color
:
var
(
--ink
);
font-family
:
var
(
--sans
);
font-size
:
16px
;
line-height
:
1.9
;
-webkit-font-smoothing
:
antialiased
}
::selection
{
background
:
var
(
--brand-soft
);
color
:
var
(
--ink
)}
a
{
color
:
var
(
--brand
);
text-decoration-color
:
color-mix
(
in
srgb
,
var
(
--brand
)
35%
,
transparent
);
text-underline-offset
:
4px
}
a
:hover
{
text-decoration-color
:
var
(
--brand
)}
button
{
font
:
inherit
}
a
:focus-visible
,
button
:focus-visible
{
outline
:
2px
solid
var
(
--brand
);
outline-offset
:
4px
}
.wrap
{
max-width
:
1040px
;
margin
:
auto
;
padding
:
40px
26px
48px
}
.document-header
{
padding
:
0
8px
28px
}
.topline
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
16px
;
align-items
:
center
;
margin-bottom
:
16px
}
.eyebrow
{
font-size
:
12px
;
letter-spacing
:
.10em
;
color
:
var
(
--brand
);
font-weight
:
700
}
.tools
{
display
:
flex
;
gap
:
4px
}
.tools
button
{
border
:
0
;
color
:
var
(
--ink-3
);
background
:
transparent
;
padding
:
5px
8px
;
cursor
:
pointer
;
font-size
:
12px
;
border-radius
:
5px
;
display
:
flex
;
align-items
:
center
;
gap
:
5px
}
.tools
button
:hover
{
background
:
var
(
--brand-soft
);
color
:
var
(
--brand
)}
.tools
svg
{
width
:
15px
;
height
:
15px
}
h1
{
font-family
:
var
(
--serif
);
font-weight
:
700
;
font-size
:
clamp
(
30px
,
3.8vw
,
42px
);
line-height
:
1.35
;
letter-spacing
:
.015em
;
margin
:
0
0
14px
}
.meta
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
gap
:
9px
;
color
:
var
(
--ink-3
);
font-size
:
12px
;
margin-bottom
:
14px
}
.meta
.version
{
background
:
var
(
--brand-soft
);
color
:
var
(
--brand
);
border-radius
:
99px
;
padding
:
1px
10px
;
font-weight
:
500
}
.meta
time
{
font-family
:
var
(
--mono
);
font-size
:
12px
}
.scope
{
max-width
:
48em
;
color
:
var
(
--ink-2
);
font-size
:
14px
;
line-height
:
1.8
;
margin
:
0
}
main
{
background
:
var
(
--surface
);
border-radius
:
14px
;
padding
:
42px
58px
40px
}
p
{
margin
:
0
0
18px
;
color
:
var
(
--ink-2
)}
strong
{
color
:
var
(
--ink
);
font-weight
:
600
}
p
:last-child
{
margin-bottom
:
0
}
.boundary
{
padding
:
0
0
0
20px
;
border-left
:
3px
solid
var
(
--brand
);
font-size
:
18px
;
line-height
:
1.85
;
margin-bottom
:
24px
}
.boundary
strong
{
font-weight
:
600
}
.principle
{
font-size
:
15.5px
;
line-height
:
1.9
;
margin-bottom
:
30px
}
.contents
{
border-top
:
1px
solid
var
(
--rule
);
border-bottom
:
1px
solid
var
(
--rule
);
padding
:
17px
0
19px
;
margin
:
32px
0
0
}
.contents-label
{
color
:
var
(
--ink-3
);
font-size
:
11px
;
letter-spacing
:
.14em
;
display
:
block
;
margin-bottom
:
9px
}
.contents-links
{
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
gap
:
7px
18px
}
.contents
a
{
text-decoration
:
none
;
color
:
var
(
--ink-2
);
font-size
:
13px
;
white-space
:
nowrap
}
.contents
a
:hover
{
color
:
var
(
--brand
)}
.contents
a
span
{
font-family
:
var
(
--mono
);
display
:
inline-block
;
width
:
29px
;
color
:
var
(
--brand
);
font-size
:
11px
}
.chapter
{
padding-top
:
46px
;
scroll-margin-top
:
28px
}
.chapter
+
.chapter
{
margin-top
:
42px
;
border-top
:
1px
solid
var
(
--rule
)}
.chapter-head
{
display
:
flex
;
align-items
:
baseline
;
gap
:
15px
;
margin-bottom
:
24px
}
.chapter-num
{
color
:
var
(
--brand
);
font-family
:
var
(
--mono
);
font-size
:
15px
;
line-height
:
1.3
;
flex
:
none
}
.chapter
h2
{
font-family
:
var
(
--serif
);
font-size
:
26px
;
line-height
:
1.45
;
font-weight
:
700
;
margin
:
0
;
text-wrap
:
pretty
}
.chapter
h3
{
font-size
:
18px
;
line-height
:
1.6
;
color
:
var
(
--ink
);
margin
:
32px
0
14px
;
font-weight
:
600
}
.chapter-head
+
h3
{
margin-top
:
0
}
.step-heading
{
display
:
flex
;
gap
:
8px
}
.step-heading
span
{
color
:
var
(
--brand
);
font-weight
:
500
}
.figure-intro
{
font-size
:
14px
;
margin-bottom
:
12px
}
ul
,
ol
{
padding-left
:
23px
;
color
:
var
(
--ink-2
);
margin
:
14px
0
22px
}
li
{
padding-left
:
3px
;
margin
:
7px
0
}
li
::marker
{
color
:
var
(
--brand
)}
blockquote
{
margin
:
24px
0
;
padding
:
20px
24px
;
background
:
var
(
--brand-soft
);
border-left
:
3px
solid
var
(
--brand
)}
blockquote
p
{
color
:
var
(
--ink
);
font-family
:
var
(
--serif
);
font-size
:
20px
;
line-height
:
1.75
;
font-weight
:
600
}
.consequence
{
border-left
:
3px
solid
var
(
--warn-rule
);
background
:
var
(
--warn-bg
);
padding
:
17px
21px
;
margin
:
22px
0
}
.consequence
strong
{
font-weight
:
500
}
.own-business
{
border-top
:
1px
dashed
var
(
--rule
);
padding-top
:
20px
;
margin-top
:
24px
}
.future
{
margin
:
28px
0
0
;
padding
:
18px
23px
;
background
:
var
(
--note
);
font-size
:
14px
;
border-left
:
2px
solid
var
(
--rule
)}
.future
p
{
margin-bottom
:
8px
}
.future
ul
{
margin
:
7px
0
0
}
.future
li
{
margin
:
4px
0
}
.duties
{
list-style
:
none
;
padding
:
0
;
counter-reset
:
duty
;
margin
:
20px
0
24px
}
.duties
li
{
counter-increment
:
duty
;
position
:
relative
;
padding
:
15px
0
15px
42px
;
margin
:
0
;
border-top
:
1px
solid
var
(
--rule
)}
.duties
li
:last-child
{
border-bottom
:
1px
solid
var
(
--rule
)}
.duties
li
::before
{
content
:
'0'
counter
(
duty
);
position
:
absolute
;
left
:
0
;
color
:
var
(
--brand
);
font
:
14px
var
(
--mono
);
top
:
21px
}
.questions
{
padding-left
:
23px
}
.questions
li
{
margin
:
12px
0
}
.questions
strong
{
color
:
var
(
--brand
)}
.table-wrap
{
margin
:
22px
0
26px
}
table
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
14px
;
line-height
:
1.8
;
text-align
:
left
}
th
{
font-weight
:
600
;
color
:
var
(
--ink
);
background
:
var
(
--sunken
);
padding
:
12px
16px
;
border-bottom
:
1px
solid
var
(
--rule
);
vertical-align
:
top
}
td
{
padding
:
13px
16px
;
border-bottom
:
1px
solid
var
(
--rule
);
vertical-align
:
top
;
color
:
var
(
--ink-2
)}
tbody
td
:first-child
{
font-weight
:
500
;
color
:
var
(
--ink
)}
.two-col
td
:first-child
{
width
:
37%
}
.three-col
th
:nth-child
(
1
)
{
width
:
33%
}
.three-col
th
:nth-child
(
2
)
{
width
:
28%
}
.three-col
th
:nth-child
(
3
)
{
width
:
39%
}
#review
td
:first-child
{
width
:
22%
;
color
:
var
(
--brand
);
font-weight
:
600
}
figure
{
margin
:
0
}
.flow
{
padding
:
20px
24px
22px
;
border
:
1px
solid
var
(
--rule
);
border-radius
:
9px
;
margin
:
14px
0
22px
;
background
:
var
(
--note
)}
figcaption
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--ink
);
display
:
flex
;
gap
:
10px
;
align-items
:
center
;
flex-wrap
:
wrap
;
padding-bottom
:
18px
}
figcaption
>
span
{
font-size
:
11px
;
font-family
:
var
(
--mono
);
color
:
var
(
--brand
);
font-weight
:
400
}
figcaption
>
small
{
font-size
:
11px
;
font-weight
:
400
;
color
:
var
(
--ink-3
);
margin-left
:
auto
}
.flow-grid
{
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1
fr
)
104px
minmax
(
0
,
1.1
fr
)
65px
minmax
(
0
,
1
fr
);
grid-template-rows
:
auto
auto
auto
;
align-items
:
center
}
.capture
{
grid-column
:
1
;
grid-row
:
1
;
text-align
:
center
;
font-size
:
11px
;
color
:
var
(
--ink-3
);
line-height
:
1.7
;
align-self
:
end
;
padding
:
0
0
7px
}
.capture
>
span
{
display
:
block
;
color
:
var
(
--ink-3
);
font-size
:
20px
;
line-height
:
1.3
}
.system
{
grid-row
:
2
;
background
:
var
(
--surface
);
border
:
1px
solid
var
(
--rule
);
border-top
:
2px
solid
var
(
--brand
);
padding
:
16px
8px
;
text-align
:
center
;
align-self
:
stretch
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
min-width
:
0
}
.meyer
{
grid-column
:
1
}
.saas
{
grid-column
:
3
;
background
:
var
(
--brand-soft
)}
.pac
{
grid-column
:
5
}
.system-label
{
color
:
var
(
--ink-3
);
font-size
:
10px
;
letter-spacing
:
.10em
;
line-height
:
1.5
;
margin-bottom
:
6px
}
.system
>
strong
{
font-size
:
18px
;
line-height
:
1.5
;
font-weight
:
600
;
white-space
:
nowrap
}
.system
>
small
{
font-size
:
11px
;
color
:
var
(
--ink-2
);
margin-top
:
6px
;
line-height
:
1.7
}
.connector
{
grid-row
:
2
;
text-align
:
center
;
color
:
var
(
--brand
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
8px
;
padding
:
0
5px
}
.connector.first
{
grid-column
:
2
}
.connector.second
{
grid-column
:
4
}
.connector
>
span
{
font-size
:
11px
;
line-height
:
1.6
}
.connector
svg
{
width
:
38px
;
height
:
12px
;
flex
:
none
}
.connector
small
{
font-size
:
10px
;
color
:
var
(
--ink-3
);
line-height
:
1.5
}
.storage
{
grid-column
:
1
;
grid-row
:
3
;
text-align
:
center
;
color
:
var
(
--ink-3
);
font-size
:
13px
}
.storage
>
span
{
display
:
block
;
line-height
:
1.4
;
font-size
:
20px
}
.storage
>
div
{
border
:
1px
dashed
var
(
--rule
);
background
:
var
(
--surface
);
padding
:
4px
10px
}
.storage
small
{
font-size
:
11px
;
margin-left
:
5px
}
.mobile-only
{
display
:
none
}
.view-path
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
19px
20px
;
border-top
:
1px
solid
var
(
--rule
);
border-bottom
:
1px
solid
var
(
--rule
);
margin
:
18px
0
;
color
:
var
(
--ink
);
font-size
:
14px
;
font-weight
:
500
}
.view-path
b
{
color
:
var
(
--brand
);
font-size
:
18px
;
font-weight
:
400
;
line-height
:
1
}
.sources
{
margin-top
:
44px
;
padding-top
:
25px
;
border-top
:
1px
solid
var
(
--rule
);
scroll-margin-top
:
28px
;
font-size
:
12.5px
;
color
:
var
(
--ink-3
);
line-height
:
1.85
}
.sources
h2
{
font-size
:
14px
;
font-weight
:
600
;
margin
:
0
0
12px
}
.sources
p
{
color
:
var
(
--ink-3
);
margin-bottom
:
13px
}
.sources
ul
{
padding-left
:
19px
;
margin
:
14px
0
17px
}
.sources
li
{
padding-left
:
1px
;
margin
:
8px
0
;
color
:
var
(
--ink-3
)}
.sources
a
{
font-weight
:
500
}
.page-footer
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
color
:
var
(
--ink-3
);
font-size
:
11px
;
padding
:
19px
8px
0
}
.page-footer
a
{
text-decoration
:
none
;
color
:
var
(
--ink-3
)}
.back-top
{
position
:
fixed
;
right
:
24px
;
bottom
:
22px
;
z-index
:
10
;
border
:
1px
solid
var
(
--rule
);
border-radius
:
99px
;
padding
:
8px
14px
;
background
:
var
(
--surface
);
color
:
var
(
--brand
);
font-size
:
12px
;
text-decoration
:
none
;
box-shadow
:
0
2px
16px
#00000006
;
opacity
:
0
;
pointer-events
:
none
;
transition
:
opacity
.2s
}
.back-top.visible
{
opacity
:
1
;
pointer-events
:
auto
}
.skip-link
{
position
:
absolute
;
top
:
-80px
;
left
:
20px
;
background
:
var
(
--surface
);
padding
:
10px
;
z-index
:
20
}
.skip-link
:focus
{
top
:
10px
}
@media
(
min-width
:
1440px
){
.wrap
{
padding-top
:
48px
}}
@media
(
max-width
:
900px
){
.wrap
{
padding
:
26px
22px
32px
}
main
{
padding
:
34px
35px
}
.chapter
h2
{
font-size
:
24px
}
.flow
{
padding
:
18px
16px
}
.flow-grid
{
grid-template-columns
:
minmax
(
0
,
1
fr
)
90px
minmax
(
0
,
1.1
fr
)
56px
minmax
(
0
,
1
fr
)}
.system
>
strong
{
font-size
:
16px
}
.system
>
small
{
font-size
:
9px
}
.system-label
{
font-size
:
9px
}
.connector
>
span
{
font-size
:
9px
}
.connector
small
{
font-size
:
8px
}}
@media
(
max-width
:
660px
){
body
{
font-size
:
15px
;
line-height
:
1.85
}
.wrap
{
padding
:
22px
15px
28px
}
.document-header
{
padding
:
0
4px
23px
}
.topline
{
align-items
:
flex-start
;
gap
:
8px
;
margin-bottom
:
13px
}
.eyebrow
{
font-size
:
10px
;
letter-spacing
:
.04em
;
padding-top
:
7px
}
.tools
{
gap
:
0
;
flex
:
none
}
.tools
button
{
padding
:
7px
}
.tools
button
span
{
display
:
none
}
.tools
svg
{
width
:
16px
;
height
:
16px
}
h1
span
{
display
:
block
}
h1
{
font-size
:
31px
;
line-height
:
1.4
;
max-width
:
11em
;
text-wrap
:
balance
;
margin-bottom
:
13px
}
.meta
{
font-size
:
11px
;
margin-bottom
:
12px
}
.meta
time
{
font-size
:
11px
}
.scope
{
font-size
:
13px
;
line-height
:
1.8
}
main
{
padding
:
27px
22px
29px
;
border-radius
:
10px
}
.boundary
{
font-size
:
17px
;
line-height
:
1.85
;
padding-left
:
15px
;
margin-bottom
:
21px
}
.principle
{
font-size
:
14px
}
.contents
{
margin-top
:
25px
;
padding
:
15px
0
17px
}
.contents-links
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
gap
:
7px
12px
}
.contents
a
{
font-size
:
12px
}
.contents
a
span
{
width
:
25px
}
.chapter
{
padding-top
:
32px
;
scroll-margin-top
:
18px
}
.chapter
+
.chapter
{
margin-top
:
30px
}
.chapter-head
{
gap
:
10px
;
margin-bottom
:
21px
}
.chapter-num
{
font-size
:
12px
}
.chapter
h2
{
font-size
:
22px
;
line-height
:
1.5
}
.chapter
h3
{
font-size
:
16px
;
margin-top
:
28px
}
.chapter-head
+
h3
{
margin-top
:
0
}
.consequence
{
padding
:
14px
16px
}
.chapter
blockquote
{
padding
:
16px
18px
;
margin
:
22px
0
}
.chapter
blockquote
p
{
font-size
:
18px
;
line-height
:
1.8
}
.table-wrap
{
margin
:
22px
0
}
.table-wrap
thead
{
display
:
none
}
.table-wrap
table
,
.table-wrap
tbody
{
display
:
block
}
.table-wrap
tr
{
display
:
block
;
padding
:
13px
0
;
border-bottom
:
1px
solid
var
(
--rule
)}
.table-wrap
tr
:first-child
{
border-top
:
2px
solid
var
(
--brand
)}
.table-wrap
td
{
display
:
block
;
padding
:
3px
0
;
border
:
0
;
width
:
auto
!important
;
line-height
:
1.85
;
font-size
:
13px
}
.table-wrap
td
:first-child
{
font-size
:
14px
;
font-weight
:
600
;
padding
:
0
0
6px
;
color
:
var
(
--ink
)}
.three-col
td
:not
(
:first-child
)
{
display
:
grid
;
grid-template-columns
:
6.4em
1
fr
;
gap
:
10px
}
.three-col
td
:not
(
:first-child
)
::before
{
content
:
attr
(
data-label
);
font-size
:
11px
;
color
:
var
(
--ink-3
);
padding-top
:
2px
}
#review
td
:first-child
{
color
:
var
(
--brand
)}
.flow
{
padding
:
16px
;
margin
:
13px
0
20px
}
figcaption
{
font-size
:
12px
;
gap
:
8px
;
padding-bottom
:
18px
}
figcaption
>
small
{
display
:
block
;
flex-basis
:
100%
;
font-size
:
10px
;
margin
:
0
}
.flow-grid
{
display
:
flex
;
flex-direction
:
column
;
gap
:
0
}
.capture
{
font-size
:
11px
;
align-self
:
center
;
padding-bottom
:
4px
}
.system
{
align-self
:
stretch
;
padding
:
14px
;
min-height
:
95px
}
.system
>
strong
{
font-size
:
19px
}
.system
>
small
{
font-size
:
11px
}
.system-label
{
font-size
:
10px
;
margin-bottom
:
3px
}
.connector
{
padding
:
10px
0
;
gap
:
4px
}
.connector
svg
{
transform
:
rotate
(
90deg
);
width
:
28px
;
height
:
12px
;
margin
:
6px
0
}
.connector
>
span
{
font-size
:
11px
}
.connector
>
span
br
{
display
:
none
}
.connector
small
{
font-size
:
10px
}
.storage
{
margin-top
:
16px
;
align-self
:
stretch
;
font-size
:
12px
}
.storage
>
span
{
display
:
none
}
.storage
>
div
{
padding
:
7px
5px
;
background
:
transparent
}
.storage
small
{
font-size
:
11px
}
.mobile-only
{
display
:
inline
;
font-weight
:
400
}
.view-path
{
flex-direction
:
column
;
gap
:
7px
;
padding
:
16px
;
font-size
:
13px
;
align-items
:
flex-start
}
.view-path
b
{
transform
:
rotate
(
90deg
);
margin-left
:
7px
;
font-size
:
17px
;
line-height
:
1.1
}
.duties
li
{
padding-left
:
33px
}
.duties
li
::before
{
font-size
:
12px
;
top
:
20px
}
.future
{
font-size
:
13px
;
padding
:
15px
17px
}
.sources
{
margin-top
:
32px
;
font-size
:
11.5px
}
.sources
h2
{
font-size
:
13px
}
.page-footer
{
font-size
:
10px
;
flex-direction
:
column
;
gap
:
3px
;
padding
:
15px
4px
0
}
.back-top
{
right
:
12px
;
bottom
:
12px
;
font-size
:
11px
;
padding
:
6px
11px
}}
@media
(
max-width
:
350px
){
.wrap
{
padding-inline
:
10px
}
main
{
padding-inline
:
17px
}
h1
{
font-size
:
29px
}
.eyebrow
{
font-size
:
9px
}
.boundary
{
font-size
:
16px
}
.chapter
h2
{
font-size
:
20px
}
.flow
{
padding-inline
:
12px
}}
@media
(
prefers-reduced-motion
:
reduce
){
html
{
scroll-behavior
:
auto
}
*
{
transition
:
none
!important
}}
@page
{
size
:
A4
;
margin
:
16mm
17mm
17mm
}
@media
print
{
html
{
scroll-behavior
:
auto
}
:root
,
:root
[
data-theme
=
dark
]
{
color-scheme
:
light
;
--ground
:
#fff
;
--surface
:
#fff
;
--sunken
:
#edf0f6
;
--ink
:
#0f1b33
;
--ink-2
:
#33415a
;
--ink-3
:
#69758a
;
--brand
:
#0032a0
;
--brand-soft
:
#edf1fa
;
--rule
:
#d5dce9
;
--note
:
#f8f9fc
;
--warn-bg
:
#fffaf0
;
--warn-rule
:
#c1a26b
}
body
{
background
:
white
;
font-size
:
10pt
;
line-height
:
1.75
;
-webkit-print-color-adjust
:
exact
;
print-color-adjust
:
exact
}
.wrap
{
padding
:
0
;
max-width
:
none
}
.document-header
{
padding
:
0
0
17pt
}
.topline
{
margin-bottom
:
9pt
}
.eyebrow
{
font-size
:
8pt
}
h1
{
font-size
:
25pt
;
margin-bottom
:
8pt
}
.meta
{
margin-bottom
:
7pt
;
font-size
:
8pt
}
.meta
time
{
font-size
:
8pt
}
.scope
{
font-size
:
9pt
}
.tools
,
.back-top
,
.skip-link
{
display
:
none
!important
}
main
{
padding
:
0
;
border-radius
:
0
}
.boundary
{
font-size
:
11.5pt
;
margin-bottom
:
13pt
;
padding-left
:
12pt
}
.principle
{
font-size
:
9.5pt
;
margin-bottom
:
17pt
}
.contents
{
margin-top
:
17pt
;
padding
:
10pt
0
}
.contents-links
{
grid-template-columns
:
repeat
(
3
,
1
fr
);
gap
:
4pt
10pt
}
.contents
a
{
font-size
:
8pt
}
.contents-label
{
font-size
:
7pt
;
margin-bottom
:
5pt
}
.chapter
{
padding-top
:
24pt
}
.chapter
+
.chapter
{
margin-top
:
23pt
}
.chapter-head
{
gap
:
10pt
;
margin-bottom
:
13pt
;
break-after
:
avoid
}
.chapter
h2
{
font-size
:
17pt
;
line-height
:
1.45
}
.chapter-num
{
font-size
:
10pt
}
.chapter
h3
{
font-size
:
11.5pt
;
margin
:
19pt
0
9pt
;
break-after
:
avoid
}
.chapter-head
+
h3
{
margin-top
:
0
}
p
{
margin-bottom
:
10pt
;
orphans
:
3
;
widows
:
3
}
li
{
margin
:
4pt
0
}
ul
,
ol
{
margin
:
9pt
0
13pt
}
blockquote
{
margin
:
13pt
0
;
padding
:
12pt
16pt
;
break-inside
:
avoid
}
blockquote
p
{
font-size
:
12pt
}
.consequence
{
padding
:
10pt
14pt
;
margin
:
12pt
0
;
break-inside
:
avoid
}
.figure-intro
{
font-size
:
9pt
;
break-after
:
avoid
;
margin-bottom
:
7pt
}
.flow
{
margin
:
8pt
0
13pt
;
padding
:
12pt
14pt
;
break-inside
:
avoid
}
.flow-grid
{
grid-template-columns
:
minmax
(
0
,
1
fr
)
85px
minmax
(
0
,
1.1
fr
)
55px
minmax
(
0
,
1
fr
)}
figcaption
{
font-size
:
8pt
;
padding-bottom
:
12pt
}
figcaption
>
span
{
font-size
:
7pt
}
figcaption
>
small
{
font-size
:
7pt
}
.capture
{
font-size
:
7pt
}
.system
{
padding
:
10pt
5pt
}
.system-label
{
font-size
:
6.5pt
}
.system
>
strong
{
font-size
:
12pt
}
.system
>
small
{
font-size
:
6.5pt
}
.connector
>
span
{
font-size
:
6.5pt
}
.connector
small
{
font-size
:
5.5pt
}
.storage
{
font-size
:
8pt
}
.storage
small
{
font-size
:
7pt
}
.view-path
{
font-size
:
8.5pt
;
padding
:
12pt
10pt
;
break-inside
:
avoid
;
margin
:
11pt
0
}
.table-wrap
{
margin
:
13pt
0
16pt
;
break-inside
:
avoid
}
table
{
font-size
:
8.5pt
;
line-height
:
1.65
}
th
{
padding
:
8pt
10pt
}
td
{
padding
:
8pt
10pt
}
tr
{
break-inside
:
avoid
}
.duties
{
break-inside
:
avoid
}
.duties
li
{
padding
:
9pt
0
9pt
29pt
}
.duties
li
::before
{
font-size
:
9pt
;
top
:
13pt
}
.future
{
font-size
:
8.5pt
;
break-inside
:
avoid
;
padding
:
12pt
15pt
;
margin-top
:
17pt
}
.own-business
{
padding-top
:
12pt
;
margin-top
:
15pt
}
.sources
{
font-size
:
8pt
;
line-height
:
1.75
;
margin-top
:
25pt
;
padding-top
:
17pt
}
.sources
h2
{
font-size
:
9.5pt
;
break-after
:
avoid
}
.sources
li
{
margin
:
6pt
0
}
.sources
p
{
margin-bottom
:
9pt
}
.page-footer
{
font-size
:
7pt
;
padding-top
:
15pt
}
a
{
color
:
inherit
;
text-decoration
:
none
}
.sources
a
{
text-decoration
:
underline
;
text-decoration-color
:
#b9c3d8
}
.questions
{
break-inside
:
avoid
;
break-after
:
avoid
}
.review-conclusion
{
break-before
:
avoid
}
.table-wrap
:has
(+
p
)
{
break-after
:
auto
}
.keep-next
{
break-after
:
avoid
}}
</style></head>
<body>
<aside
role=
"note"
style=
"padding:12px 20px;background:#fff4da;color:#594100;border-bottom:1px solid #e9cf8d;font:14px/1.7 sans-serif"
>
历史评审稿,2026-10-22 归档。事件推送方案未被采用;现行实现按诊所分页轮询并批量查询分析。
<a
href=
"../../../apps/pac-service/src/integrations/meyer/README.md"
>
查看当前采集说明
</a></aside><a
class=
"skip-link"
href=
"#main"
>
跳到正文
</a><div
class=
"wrap"
id=
"top"
>
<header
class=
"document-header"
><div
class=
"topline"
><div
class=
"eyebrow"
>
美亚 × FridaySaaS × PAC
</div><div
class=
"tools"
aria-label=
"文档工具"
><button
type=
"button"
id=
"theme-button"
aria-label=
"切换深浅色"
aria-pressed=
"false"
title=
"切换深浅色"
><svg
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
aria-hidden=
"true"
><circle
cx=
"12"
cy=
"12"
r=
"8"
/><path
d=
"M12 4a8 8 0 0 1 0 16Z"
fill=
"currentColor"
stroke=
"none"
/></svg><span>
深浅色
</span></button><button
type=
"button"
id=
"print-button"
aria-label=
"打印或另存为 PDF"
title=
"打印或另存为 PDF"
><svg
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.5"
aria-hidden=
"true"
><path
d=
"M6 9V3h12v6M6 18H3v-8h18v8h-3M6 14h12v7H6Z"
/></svg><span>
打印
</span></button></div></div><h1><span>
影像与分析数据
</span><span>
方案设计
</span></h1><div
class=
"meta"
><span
class=
"version"
>
内部评审稿
</span><span>
v0.2
</span><span
aria-hidden=
"true"
>
·
</span><time
datetime=
"2026-10-16"
>
2026-10-16
</time></div><p
class=
"scope"
>
本轮先确认设计方向与系统边界;字段、表结构、调度周期和开发任务在方案采纳后细化。
</p></header>
<main
id=
"main"
><div
class=
"opening"
><p
class=
"boundary"
><strong>
建议边界:美亚管理影像资产,FridaySaaS 管理业务归属并负责对外同步,PAC 消费宿主提供的事实。PAC 核心不直接接美亚。
</strong></p>
<p
class=
"principle"
>
判断依据是:
<strong>
三个系统可以保存和使用同一份患者与影像关系,但应明确各自数据的权威来源,并让关联及其变更沿统一链路传递。
</strong>
FridaySaaS 提供业务患者身份,美亚维护影像资产及其患者映射;SaaS 将两者对应关系校验、留存后提供给 PAC,PAC 据此形成事实和诊断信号。
</p></div><nav
class=
"contents"
id=
"contents"
aria-label=
"文档目录"
><span
class=
"contents-label"
>
目录
</span><div
class=
"contents-links"
><a
href=
"#relationship"
><span>
01
</span>
整体关系
</a><a
href=
"#meyer"
><span>
02
</span>
美亚
</a><a
href=
"#saas"
><span>
03
</span>
FridaySaaS
</a><a
href=
"#pac"
><span>
04
</span>
PAC
</a><a
href=
"#review"
><span>
05
</span>
评审与确认
</a><a
href=
"#sources"
><span>
附
</span>
资料依据
</a></div></nav>
<section
class=
"chapter"
id=
"relationship"
aria-labelledby=
"heading-1"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
01
</span><h2
id=
"heading-1"
>
整体关系:数据供给与查看原片分开
</h2></div><h3
class=
"step-heading"
><span>
①
</span>
关联基础:先明确影像属于谁
</h3>
<p>
FridaySaaS 向美亚同步患者、诊所和就诊信息,美亚据此关联影像;SaaS 校验并留存对应关系,再向 PAC 提供。
</p>
<h3
class=
"step-heading"
><span>
②
</span>
数据供给:同步影像信息与分析结果
</h3>
<p
class=
"figure-intro"
>
以下以启用美亚新影像为例,表示目标链路:
</p>
<figure
class=
"flow"
aria-labelledby=
"flow-caption"
>
<figcaption
id=
"flow-caption"
><span>
图 01
</span>
影像与分析的数据供给
<small>
目标链路 · 新影像启用时
</small></figcaption>
<div
class=
"flow-grid"
>
<div
class=
"capture"
>
诊所设备与前置机
<span
aria-hidden=
"true"
>
↓
</span></div>
<div
class=
"system meyer"
><span
class=
"system-label"
>
影像资产
</span><strong>
美亚 PACS
</strong><small>
采集接入 · 影像与分析
</small></div>
<div
class=
"connector first"
><span>
影像信息
<br>
分析结果及变更
</span><svg
viewBox=
"0 0 40 12"
fill=
"none"
aria-hidden=
"true"
><path
d=
"M1 6h36m-5-4 5 4-5 4"
stroke=
"currentColor"
stroke-width=
"1.3"
></path></svg><small>
获取方式待确认
</small></div>
<div
class=
"system saas"
><span
class=
"system-label"
>
业务归属
</span><strong>
FridaySaaS
</strong><small>
校验归属 · 留存 · 同步
</small></div>
<div
class=
"connector second"
><span>
宿主事实
</span><svg
viewBox=
"0 0 40 12"
fill=
"none"
aria-hidden=
"true"
><path
d=
"M1 6h36m-5-4 5 4-5 4"
stroke=
"currentColor"
stroke-width=
"1.3"
></path></svg></div>
<div
class=
"system pac"
><span
class=
"system-label"
>
信号应用
</span><strong>
PAC
</strong><small>
诊断信号 · 画像与召回
</small></div>
<div
class=
"storage"
><span
aria-hidden=
"true"
>
↓
</span><div><b
class=
"mobile-only"
>
美亚 →
</b>
OSS
<small>
原片存储
</small></div></div>
</div>
</figure>
<p>
原片由美亚管理,SaaS 向 PAC 提供影像业务信息与分析结果。美亚到 SaaS 如何获取变更,仍需确认。
</p>
<p>
新老影像功能可以保留,实际使用时择一启用;选用旧影像时,也由 SaaS 统一向 PAC 提供数据。
</p>
<h3
class=
"step-heading"
><span>
③
</span>
查看原片:用户按需打开宿主入口
</h3>
<div
class=
"view-path"
role=
"group"
aria-label=
"查看原片路径"
><span>
PAC 宿主槽位
</span><b
aria-hidden=
"true"
>
→
</b><span>
FridaySaaS 影像页面
</span><b
aria-hidden=
"true"
>
→
</b><span>
当前选用的影像系统
</span></div>
<p>
SaaS 负责权限和入口;启用新影像时,由美亚提供阅片服务。
<strong>
数据同步持续进行,查看原片由用户按需发起。
</strong></p>
</section>
<section
class=
"chapter"
id=
"meyer"
aria-labelledby=
"heading-2"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
02
</span><h2
id=
"heading-2"
>
美亚:最重要的承诺是“变化可获取”
</h2></div><p>
美亚负责采集接入、原片存储、影像关联、阅片,以及其提供的分析。对外不仅要能查询当前数据,还要让 SaaS 知道数据发生了什么变化。
</p>
<p
class=
"consequence"
><strong>
如果只能查新影像列表,SaaS 可以知道“多了一张片”,却未必知道这张片后来分析完成、被改绑到另一位患者,或被撤销。下游就可能继续使用过期甚至归属错误的依据。
</strong></p>
<p>
因此,对美亚真正要提的要求是:
</p>
<blockquote>
<p>
提供可追溯、可补拉的影像及分析变更机制,包含新增、修改、撤销和归属变化。
</p>
</blockquote>
<div
class=
"table-wrap"
><table
class=
"three-col"
>
<thead>
<tr>
<th
scope=
"col"
>
美亚提供的能力
</th>
<th
scope=
"col"
>
FridaySaaS 如何获取
</th>
<th
scope=
"col"
>
意味着什么
</th>
</tr>
</thead>
<tbody><tr>
<td
data-label=
"美亚提供的能力"
>
有事件通知,并能补查变更
</td>
<td
data-label=
"FridaySaaS 如何获取"
>
通知后同步,遗漏时补拉
</td>
<td
data-label=
"意味着什么"
>
及时性较好,也能恢复遗漏
</td>
</tr>
<tr>
<td
data-label=
"美亚提供的能力"
>
没有通知,有完整变更查询
</td>
<td
data-label=
"FridaySaaS 如何获取"
>
定时拉取
</td>
<td
data-label=
"意味着什么"
>
可以可靠同步,延迟取决于拉取周期
</td>
</tr>
<tr>
<td
data-label=
"美亚提供的能力"
>
只有入库时间列表、逐张查询
</td>
<td
data-label=
"FridaySaaS 如何获取"
>
定时发现新片,反复查询分析
</td>
<td
data-label=
"意味着什么"
>
能做部分同步,但后续修订、改绑、撤销的完整性没有保障
</td>
</tr>
</tbody></table></div>
</section>
<section
class=
"chapter"
id=
"saas"
aria-labelledby=
"heading-3"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
03
</span><h2
id=
"heading-3"
>
FridaySaaS:留业务记录,统一新老来源
</h2></div><h3>
3.1 为什么需要留新影像数据
</h3>
<p><strong>
如果 SaaS 只在用户打开页面时代理查询美亚,它承担的是展示功能,无法承担稳定的下游数据供给。
</strong>
没人打开页面时,PAC 仍需要知道患者有了新的影像或分析。
</p>
<p
class=
"keep-next"
>
既然 PAC 从 SaaS 接收数据,SaaS 就应保存一份能够重试、重推、核对的影像业务记录:
</p>
<div
class=
"table-wrap"
><table
class=
"two-col"
>
<thead>
<tr>
<th
scope=
"col"
>
应保存的信息
</th>
<th
scope=
"col"
>
用途
</th>
</tr>
</thead>
<tbody><tr>
<td
data-label=
"应保存的信息"
>
患者、租户与影像采集诊所
</td>
<td
data-label=
"用途"
>
明确业务归属;采集诊所不随患者后续转店而改变
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
影像来源、影像与检查标识
</td>
<td
data-label=
"用途"
>
稳定识别、去重和追溯
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
类型、拍摄时间、绑定及有效状态
</td>
<td
data-label=
"用途"
>
解释这份影像的业务含义
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
稳定文件引用,如 OSS ID
</td>
<td
data-label=
"用途"
>
后续获取原片;临时链接过期后仍可定位
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
分析结果、来源及版本
</td>
<td
data-label=
"用途"
>
保留当时依据,支持补推和重放
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
同步进度与失败记录
</td>
<td
data-label=
"用途"
>
知道哪些已交付、哪些还需要补齐
</td>
</tr>
</tbody></table></div>
<p>
原片继续由美亚管理。SaaS 保存索引和结果,不重复建设影像存储;临时签名 URL 不能作为永久地址。
</p>
<p><strong>
这样即使美亚暂时不可用,SaaS 和 PAC 仍可查询已留存的影像信息和分析结果;原片访问和最新变更同步则可能受影响。
</strong>
分析结果会影响后续业务判断,值得留存,而不应仅在查看页面时临时取一次。
</p>
<p>
美亚接入、获取变更和归属校验由 SaaS 对接层负责;open-connector 从 SaaS 已保存的业务记录向 PAC 推送。供应商协议与下游数据传输各自负责自己的部分。
</p>
<p
class=
"own-business"
>
即使不接 PAC,留存业务记录也能支撑 SaaS 自身业务:将影像与就诊、病历、收费关联,用于检查进度跟进、复诊查阅、经营分析,以及追溯当时使用的分析结果。如果不留存,这些业务就需要反复查询美亚并临时关联数据,受其接口可用性和查询能力限制;分析结果更新或撤销后,若美亚不提供历史版本,SaaS 就难以还原当时使用的依据。
</p>
<h3>
3.2 新老影像怎么归一
</h3>
<p
class=
"keep-next"
><strong>
业务语义归一,来源实现分开。
</strong>
新老功能可以保留,实际择一使用;对下游都应能回答同一组问题:
</p>
<ul>
<li>
影像属于谁、在哪里拍摄,是什么类型、什么时候拍摄;
</li>
<li>
当前是否有效,怎样查看,有哪些分析结果。
</li>
</ul>
<p><strong>
如果下游直接依赖“老影像的一套字段”和“美亚的另一套字段”,每切换一次影像系统,下游都要跟着改。
</strong>
统一表达后,来源切换主要由 SaaS 处理,PAC 继续按宿主事实消费。
</p>
<p>
但统一不等于强行写入旧表。美亚有检查、切片、绑定状态和外部文件标识;如果旧结构表达不了,不能为了兼容丢掉它们,也不能把临时链接塞进旧文件地址就算完成。是否复用旧表,是方案采纳后的存储设计选择。
</p>
<p>
如果未来把历史影像迁入美亚,应保留新旧标识的对应关系。
<strong>
同一张片换了存储位置,不应被当成一次新的拍摄或重复的分析依据。
</strong></p>
</section>
<section
class=
"chapter"
id=
"pac"
aria-labelledby=
"heading-4"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
04
</span><h2
id=
"heading-4"
>
PAC:用分析形成诊断信号,通过宿主查看影像
</h2></div><p
class=
"keep-next"
>
PAC 在这条链路中的职责保持两项:
</p>
<ol
class=
"duties"
>
<li><strong>
消费影像分析。
</strong>
将宿主提供的有效分析结果作为诊断信号源之一,与其他患者事实共同用于画像与召回,并保留来源依据。
</li>
<li><strong>
提供影像查看入口。
</strong>
沿用宿主槽位打开 FridaySaaS,由宿主处理新老影像入口、权限和阅片授权。
</li>
</ol>
<p>
美亚生成的分析,由 SaaS 接收、留底后推给 PAC。
<strong>
如果 PAC 每次处理事实都临时向美亚取结果,重放同一份数据时可能拿到不同内容,就无法解释当时为什么得出那个判断。
</strong></p>
<p>
PAC 可以知道数据来源是美亚,但美亚不应成为 PAC 核心的直接依赖。
<strong>
否则接入下一家影像供应商时,PAC 又要接一套鉴权、患者映射和同步机制,还可能与 SaaS 的权限及归属规则形成两套判断。
</strong>
这些差异应由宿主消化。
</p>
<aside
class=
"future"
aria-label=
"后续能力边界"
><p
class=
"keep-next"
>
如果将来是 PAC 自己发起分析,那是另一项能力:
</p><ul>
<li>
PAC 通过宿主获得授权的文件;
</li>
<li>
PAC 调用自己选择的分析服务;
</li>
<li>
结果标明由 PAC 发起,保留实际分析服务来源及结果版本,并关联原始影像。
</li>
</ul></aside>
</section>
<section
class=
"chapter"
id=
"review"
aria-labelledby=
"heading-5"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
05
</span><h2
id=
"heading-5"
>
评审要确定什么,哪些仍需外部确认
</h2></div><p
class=
"keep-next"
>
本轮评审确认的是职责和数据留存方向:
</p>
<div
class=
"table-wrap"
><table
class=
"two-col"
>
<thead>
<tr>
<th
scope=
"col"
>
系统
</th>
<th
scope=
"col"
>
对下游承担的承诺
</th>
</tr>
</thead>
<tbody><tr>
<td
data-label=
"系统"
>
美亚
</td>
<td
data-label=
"对下游承担的承诺"
>
影像与分析可查询,变更可获取,原片可授权访问
</td>
</tr>
<tr>
<td
data-label=
"系统"
>
FridaySaaS
</td>
<td
data-label=
"对下游承担的承诺"
>
患者归属正确,新老影像统一表达,业务数据可留底、补推和核对
</td>
</tr>
<tr>
<td
data-label=
"系统"
>
PAC
</td>
<td
data-label=
"对下游承担的承诺"
>
消费宿主分析作为诊断信号,通过宿主入口查看影像
</td>
</tr>
</tbody></table></div>
<p
class=
"keep-next"
>
方案采纳前,优先向美亚确认两件事:
</p>
<ul
class=
"questions"
>
<li><strong>
变更怎样获取
</strong>
:有无通知;没有通知时,能否完整拉到新增、修订、撤销和归属变化,并补查遗漏。
</li>
<li><strong>
分析怎样产生和识别
</strong>
:何时触发,怎样区分未分析、处理中、失败、完成零发现,以及结果后来是否修改。
</li>
</ul>
<p
class=
"review-conclusion"
>
这两项决定后续采用通知还是轮询,以及分析能否可靠成为诊断信号。具体字段、存储结构、调度周期和任务拆分,在方向采纳、能力确认后再制定。
</p>
</section>
<aside
class=
"sources"
id=
"sources"
aria-labelledby=
"sources-title"
><h2
id=
"sources-title"
>
依据与核对范围
</h2><p
class=
"keep-next"
>
本稿依据 2026-10-16 已核对的本地代码、SDK 和以下资料;未验证美亚实际部署能力。接口细节存在版本差异,正式对接仍需双方确认。
</p>
<ul>
<li><span
title=
"本机参考文件,未随归档分发"
>
美亚系统集成方案
</span>
:第 2–3 章职责及信息同步、第 4–5 章设备与前置机、第 6–7 章阅片与患者映射。
</li>
<li><span
title=
"本机参考文件,未随归档分发"
>
美亚私有化部署方案
</span>
:明确规划阿里云 OSS 和算法资源;规划不代表当前能力已启用。
</li>
<li><span
title=
"本机参考文件,未随归档分发"
>
美亚接口文档
</span>
:§6.3 分析查询及空结果含义、§6.4/6.8 文件引用与临时 URL。
</li>
<li>
SaaS 代码:
<span
title=
"本机参考文件,未随归档分发"
>
PACS 接口
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
同步与查询
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
新版影像中心
</span>
。
</li>
<li>
PAC 代码:
<span
title=
"本机参考文件,未随归档分发"
>
影像分析事实
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
宿主影像入口
</span>
。
</li>
</ul>
<p>
资料链接指向本机原件;内部分享时可随报告提供原 PDF。
</p>
</aside></main>
<footer
class=
"page-footer"
><span>
美亚 × FridaySaaS × PAC · 影像与分析数据方案设计
</span><a
href=
"#top"
>
内部评审稿 v0.2 · 返回页首 ↑
</a></footer></div><a
class=
"back-top"
href=
"#contents"
aria-label=
"返回文档目录"
>
目录 ↑
</a>
<script>
(()
=>
{
const
root
=
document
.
documentElement
,
themeButton
=
document
.
getElementById
(
'theme-button'
),
key
=
'imaging-document-theme'
;
const
scheme
=
matchMedia
(
'(prefers-color-scheme: dark)'
);
const
isDark
=
()
=>
root
.
dataset
.
theme
?
root
.
dataset
.
theme
===
'dark'
:
scheme
.
matches
;
const
update
=
()
=>
themeButton
.
setAttribute
(
'aria-pressed'
,
String
(
isDark
()));
try
{
const
saved
=
localStorage
.
getItem
(
key
);
if
(
saved
===
'dark'
||
saved
===
'light'
)
root
.
dataset
.
theme
=
saved
;}
catch
{}
update
();
scheme
.
addEventListener
(
'change'
,
update
);
themeButton
.
addEventListener
(
'click'
,()
=>
{
const
theme
=
isDark
()?
'light'
:
'dark'
;
root
.
dataset
.
theme
=
theme
;
try
{
localStorage
.
setItem
(
key
,
theme
)}
catch
{}
update
()});
document
.
getElementById
(
'print-button'
).
addEventListener
(
'click'
,()
=>
window
.
print
());
const
back
=
document
.
querySelector
(
'.back-top'
);
const
check
=
()
=>
{
const
visible
=
scrollY
>
700
;
back
.
classList
.
toggle
(
'visible'
,
visible
);
back
.
tabIndex
=
visible
?
0
:
-
1
;
back
.
setAttribute
(
'aria-hidden'
,
String
(
!
visible
))};
addEventListener
(
'scroll'
,
check
,{
passive
:
true
});
check
();
})();
</script></body></html>
docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review.html
0 → 100644
View file @
15d08d44
<!doctype html>
<html
lang=
"zh-CN"
>
<head>
<meta
charset=
"utf-8"
><meta
name=
"viewport"
content=
"width=device-width,initial-scale=1"
><meta
name=
"color-scheme"
content=
"light dark"
>
<title>
影像与分析数据方案设计 · 美亚 × FridaySaaS × PAC
</title>
<meta
name=
"description"
content=
"影像资产、业务归属与诊断信号的三方协作方案。明确美亚、FridaySaaS 与 PAC 的数据流、留存责任与评审事项。"
>
<script>
try
{
let
t
=
localStorage
.
getItem
(
'imaging-report-theme'
);
if
(
t
===
'dark'
||
t
===
'light'
)
document
.
documentElement
.
dataset
.
theme
=
t
;
let
f
=
localStorage
.
getItem
(
'imaging-report-size'
);
if
(
f
===
'large'
)
document
.
documentElement
.
dataset
.
size
=
f
}
catch
{}
</script>
<style>
:root
{
--bg
:
#f2f1ec
;
--paper
:
#fffefb
;
--ink
:
#22312f
;
--heading
:
#172e2b
;
--muted
:
#5c6b66
;
--subtle
:
#6e7b75
;
--line
:
#dedfd6
;
--soft
:
#f5f5ef
;
--teal
:
#17665b
;
--teal-soft
:
#eaf3ed
;
--teal-line
:
#bad3c4
;
--amber
:
#80591f
;
--amber-soft
:
#faf3e6
;
--amber-line
:
#e1cfaa
;
--blue
:
#355d80
;
--blue-soft
:
#eef3f7
;
--shadow
:
0
12px
50px
#243c2b06
;
--font
:
-apple-system
,
BlinkMacSystemFont
,
"PingFang SC"
,
"Hiragino Sans GB"
,
"Microsoft YaHei"
,
sans-serif
;
--serif
:
"Songti SC"
,
"Noto Serif CJK SC"
,
"STSong"
,
"SimSun"
,
serif
;
color-scheme
:
light
}
@media
(
prefers-color-scheme
:
dark
){
:root:not
([
data-theme
=
"light"
])
{
--bg
:
#151c1b
;
--paper
:
#1c2623
;
--ink
:
#d6ded6
;
--heading
:
#edf3e9
;
--muted
:
#a4b3ab
;
--subtle
:
#96a79e
;
--line
:
#36433c
;
--soft
:
#222e28
;
--teal
:
#96cbb6
;
--teal-soft
:
#243b31
;
--teal-line
:
#466b57
;
--amber
:
#dec08a
;
--amber-soft
:
#382f21
;
--amber-line
:
#645235
;
--blue
:
#a6c4e0
;
--blue-soft
:
#253340
;
--shadow
:
none
;
color-scheme
:
dark
}}
:root
[
data-theme
=
"dark"
]
{
--bg
:
#151c1b
;
--paper
:
#1c2623
;
--ink
:
#d6ded6
;
--heading
:
#edf3e9
;
--muted
:
#a4b3ab
;
--subtle
:
#96a79e
;
--line
:
#36433c
;
--soft
:
#222e28
;
--teal
:
#96cbb6
;
--teal-soft
:
#243b31
;
--teal-line
:
#466b57
;
--amber
:
#dec08a
;
--amber-soft
:
#382f21
;
--amber-line
:
#645235
;
--blue
:
#a6c4e0
;
--blue-soft
:
#253340
;
--shadow
:
none
;
color-scheme
:
dark
}
*
{
box-sizing
:
border-box
}
html
{
scroll-behavior
:
smooth
;
scroll-padding-top
:
100px
}
body
{
margin
:
0
;
background
:
var
(
--bg
);
color
:
var
(
--ink
);
font-family
:
var
(
--font
);
font-size
:
16px
;
line-height
:
1.95
;
-webkit-font-smoothing
:
antialiased
}
a
{
color
:
var
(
--teal
);
text-decoration
:
none
}
button
,
a
,
summary
{
-webkit-tap-highlight-color
:
transparent
}
button
{
font
:
inherit
;
cursor
:
pointer
}
a
:focus-visible
,
button
:focus-visible
,
summary
:focus-visible
{
outline
:
2px
solid
var
(
--teal
);
outline-offset
:
5px
}
button
:hover
{
background
:
var
(
--soft
)}
a
:hover
{
text-decoration
:
underline
;
text-underline-offset
:
4px
}
::selection
{
background
:
var
(
--teal-line
);
color
:
var
(
--heading
)}
h1
,
h2
,
h3
,
p
,
figure
,
blockquote
{
margin
:
0
}
strong
{
font-weight
:
650
;
color
:
var
(
--heading
)}
h1
,
h2
,
h3
{
color
:
var
(
--heading
);
text-wrap
:
pretty
}
h1
{
font-family
:
var
(
--serif
);
font-size
:
52px
;
line-height
:
1.28
;
letter-spacing
:
-.025em
;
font-weight
:
700
}
h2
{
font-size
:
27px
;
line-height
:
1.6
;
font-weight
:
650
;
letter-spacing
:
-.025em
}
h3
{
font-size
:
20px
;
line-height
:
1.65
;
font-weight
:
600
;
margin
:
30px
0
17px
}
h3
:first-child
{
margin-top
:
0
}
.icon
{
width
:
20px
;
height
:
20px
;
flex
:
none
;
vertical-align
:
middle
}
.skip-link
{
position
:
fixed
;
left
:
20px
;
top
:
-70px
;
background
:
var
(
--paper
);
z-index
:
99
;
padding
:
10px
18px
;
border
:
1px
solid
var
(
--teal
)}
.skip-link
:focus
{
top
:
12px
}
.page-progress
{
position
:
fixed
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
3px
;
z-index
:
100
;
transform
:
scaleX
(
0
);
transform-origin
:
left
;
background
:
var
(
--teal
)}
.topbar
{
height
:
70px
;
position
:
sticky
;
top
:
0
;
z-index
:
30
;
background
:
var
(
--bg
);
border-bottom
:
1px
solid
var
(
--line
)}
.topbar-inner
{
max-width
:
1300px
;
height
:
100%
;
padding
:
0
38px
;
margin
:
auto
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
20px
}
.doc-mark
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
color
:
var
(
--heading
);
font-size
:
13px
;
font-weight
:
650
;
letter-spacing
:
.025em
}
.mark-icon
{
display
:
grid
;
place-items
:
center
;
width
:
30px
;
height
:
30px
;
color
:
var
(
--teal
);
border
:
1px
solid
var
(
--teal-line
);
border-radius
:
7px
}
.toolbar
{
display
:
flex
;
align-items
:
center
;
gap
:
5px
}
.tool
{
border
:
0
;
background
:
transparent
;
color
:
var
(
--muted
);
border-radius
:
6px
;
min-width
:
36px
;
min-height
:
36px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
7px
;
font-size
:
12px
;
padding
:
7px
9px
}
.tool
[
data-active
=
"true"
]
{
background
:
var
(
--teal-soft
);
color
:
var
(
--teal
)}
.print-button
{
border
:
1px
solid
var
(
--line
);
margin-left
:
5px
}
.version
{
font-size
:
11px
;
font-variant-numeric
:
tabular-nums
;
color
:
var
(
--subtle
);
margin-right
:
17px
;
letter-spacing
:
.04em
}
.size-icon
{
font-family
:
Georgia
,
serif
;
font-size
:
17px
}
.shell
{
max-width
:
1300px
;
margin
:
0
auto
;
padding
:
42px
38px
70px
;
display
:
grid
;
grid-template-columns
:
206px
minmax
(
0
,
1
fr
);
gap
:
40px
;
align-items
:
start
}
.sidebar
{
position
:
sticky
;
top
:
109px
;
padding
:
19px
0
;
max-height
:
calc
(
100vh
-
135px
);
overflow
:
auto
;
scrollbar-width
:
thin
}
.toc-label
{
font-size
:
11px
;
color
:
var
(
--subtle
);
letter-spacing
:
.15em
;
margin
:
0
0
20px
12px
}
.toc
{
display
:
grid
;
gap
:
5px
}
.toc
a
{
display
:
flex
;
gap
:
12px
;
padding
:
12px
12px
;
border-left
:
2px
solid
transparent
;
border-radius
:
0
6px
6px
0
;
font-size
:
13px
;
line-height
:
1.5
;
color
:
var
(
--muted
);
text-decoration
:
none
}
.toc
small
{
font-size
:
11px
;
font-weight
:
400
;
display
:
block
;
margin-top
:
4px
;
color
:
var
(
--subtle
)}
.toc-number
{
font-size
:
10px
;
font-variant-numeric
:
tabular-nums
;
padding-top
:
3px
;
color
:
var
(
--subtle
);
min-width
:
17px
}
.toc
a
.active
{
background
:
var
(
--teal-soft
);
color
:
var
(
--teal
);
border-color
:
var
(
--teal
);
font-weight
:
650
}
.toc
a
:hover
{
background
:
var
(
--soft
)}
.rail-note
{
border-top
:
1px
solid
var
(
--line
);
margin
:
27px
12px
0
;
padding-top
:
19px
;
color
:
var
(
--subtle
);
font-size
:
11px
;
line-height
:
1.9
}
.rail-note
b
{
display
:
block
;
font-weight
:
550
;
color
:
var
(
--muted
);
margin-bottom
:
5px
}
.reading-status
{
display
:
flex
;
justify-content
:
space-between
;
margin-top
:
20px
;
font-variant-numeric
:
tabular-nums
}
.reading-track
{
height
:
2px
;
background
:
var
(
--line
);
margin-top
:
7px
;
overflow
:
hidden
}
.reading-track
span
{
display
:
block
;
height
:
100%
;
width
:
0
;
background
:
var
(
--teal
)}
.document
{
min-width
:
0
;
background
:
var
(
--paper
);
border
:
1px
solid
var
(
--line
);
box-shadow
:
var
(
--shadow
);
border-radius
:
4px
;
padding
:
60px
58px
26px
;
position
:
relative
}
.document
:before
{
content
:
""
;
position
:
absolute
;
top
:
-1px
;
left
:
58px
;
width
:
62px
;
height
:
3px
;
background
:
var
(
--teal
)}
.hero
{
padding-bottom
:
44px
}
.eyebrow
{
display
:
flex
;
align-items
:
center
;
gap
:
9px
;
font-size
:
12px
;
color
:
var
(
--teal
);
letter-spacing
:
.09em
;
margin-bottom
:
23px
}
.eyebrow
.sep
{
color
:
var
(
--line
)}
.hero
h1
span
{
display
:
block
}
.hero-deck
{
font-family
:
var
(
--serif
);
font-size
:
21px
;
color
:
var
(
--muted
);
margin-top
:
17px
;
letter-spacing
:
.02em
}
.meta
{
display
:
flex
;
gap
:
15px
;
align-items
:
center
;
flex-wrap
:
wrap
;
font-size
:
11px
;
color
:
var
(
--subtle
);
margin
:
24px
0
0
}
.meta
.draft
{
border
:
1px
solid
var
(
--teal-line
);
padding
:
2px
8px
;
border-radius
:
3px
;
color
:
var
(
--teal
);
background
:
var
(
--teal-soft
)}
.hero-scope
{
color
:
var
(
--muted
);
font-size
:
13px
;
line-height
:
1.9
;
max-width
:
650px
;
margin-top
:
15px
}
.boundary
{
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
margin-top
:
36px
;
padding
:
25px
0
;
border-top
:
1px
solid
var
(
--line
);
border-bottom
:
1px
solid
var
(
--line
);
gap
:
25px
}
.boundary
>
div
+
div
{
border-left
:
1px
solid
var
(
--line
);
padding-left
:
25px
}
.boundary
.role-name
{
font-size
:
13px
;
font-weight
:
650
;
color
:
var
(
--teal
)}
.boundary
h2
{
font-family
:
var
(
--serif
);
font-size
:
23px
;
margin-top
:
6px
;
font-weight
:
650
;
line-height
:
1.5
}
.boundary
p
{
font-size
:
12px
;
color
:
var
(
--muted
);
line-height
:
1.8
;
margin-top
:
5px
}
.core-boundary
{
font-size
:
12px
;
margin-top
:
13px
;
color
:
var
(
--teal
);
display
:
flex
;
gap
:
7px
;
align-items
:
center
}
.core-boundary
.icon
{
width
:
15px
;
height
:
15px
}
.principle
{
padding
:
24px
0
0
;
color
:
var
(
--muted
);
font-size
:
14px
;
line-height
:
2
}
.principle
strong
{
font-family
:
var
(
--serif
);
font-size
:
20px
;
line-height
:
1.85
;
font-weight
:
600
}
.principle
p
{
margin
:
0
}
.principle
strong
{
display
:
block
;
margin
:
8px
0
13px
}
.principle
.note-label
{
margin-bottom
:
0
}
.chapter
{
padding
:
40px
0
42px
;
border-top
:
1px
solid
var
(
--line
);
scroll-margin-top
:
0
}
.chapter-head
{
display
:
flex
;
gap
:
20px
;
align-items
:
flex-start
;
margin-bottom
:
26px
}
.chapter-num
{
font-family
:
Georgia
,
serif
;
font-size
:
42px
;
line-height
:
1.3
;
color
:
var
(
--teal-line
);
font-variant-numeric
:
tabular-nums
;
font-weight
:
400
;
min-width
:
47px
;
padding-top
:
10px
}
.chapter-kicker
{
font-size
:
10px
;
letter-spacing
:
.16em
;
line-height
:
1.6
;
color
:
var
(
--teal
);
margin-bottom
:
4px
}
.prose
p
{
margin
:
16px
0
}
.prose
>
p
:first-child
{
margin-top
:
0
}
.prose
p
:last-child
{
margin-bottom
:
0
}
.prose
ul
,
.prose
ol
{
padding-left
:
24px
;
margin
:
18px
0
}
.prose
li
{
padding-left
:
3px
;
margin
:
7px
0
}
.prose
li
::marker
{
color
:
var
(
--teal
)}
.prose
>
h3
:not
(
:first-child
)
{
padding-top
:
15px
;
margin-top
:
34px
;
border-top
:
1px
dashed
var
(
--line
)}
.step-heading
{
font-size
:
16px
;
font-weight
:
600
;
color
:
var
(
--teal
);
margin-top
:
28px
!important
}
.step-heading
strong
{
color
:
var
(
--teal
)}
.figure-intro
{
font-size
:
13px
;
color
:
var
(
--muted
);
margin-bottom
:
13px
!important
}
.emphasis
{
border-left
:
3px
solid
var
(
--teal
);
padding
:
8px
0
8px
19px
;
font-size
:
16px
}
.consequence
{
background
:
var
(
--amber-soft
);
border
:
1px
solid
var
(
--amber-line
);
border-radius
:
4px
;
padding
:
19px
22px
;
font-size
:
15px
;
line-height
:
1.95
}
.consequence
strong
{
font-weight
:
500
;
color
:
var
(
--ink
)}
.note-label
{
display
:
block
;
font-size
:
11px
;
font-weight
:
600
;
letter-spacing
:
.06em
;
line-height
:
1.5
;
margin-bottom
:
10px
;
color
:
var
(
--teal
)}
.consequence
.note-label
{
color
:
var
(
--amber
)}
.requirement
{
margin
:
20px
0
25px
;
padding
:
21px
25px
;
border-left
:
3px
solid
var
(
--teal
);
background
:
var
(
--teal-soft
);
font-family
:
var
(
--serif
);
font-size
:
22px
;
line-height
:
1.8
;
color
:
var
(
--heading
)}
.requirement
p
{
margin
:
0
}
.continuity
{
border-left
:
2px
solid
var
(
--teal-line
);
padding-left
:
19px
;
font-size
:
15px
}
.continuity
strong
{
font-weight
:
600
}
.own-business
{
background
:
var
(
--soft
);
padding
:
21px
23px
;
border-radius
:
4px
;
font-size
:
14px
;
color
:
var
(
--muted
);
line-height
:
2
}
.footnote-callout
{
border-top
:
1px
solid
var
(
--line
);
padding-top
:
17px
;
font-size
:
14px
;
color
:
var
(
--muted
)}
.flow-figure
{
margin
:
17px
0
23px
;
padding
:
22px
22px
24px
;
background-color
:
var
(
--soft
);
background-image
:
radial-gradient
(
var
(
--line
)
.7px
,
transparent
.7px
);
background-size
:
15px
15px
;
border
:
1px
solid
var
(
--line
);
border-radius
:
5px
}
.flow-figure
figcaption
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
flex-wrap
:
wrap
;
font-size
:
12px
;
line-height
:
1.7
;
margin-bottom
:
21px
}
.figure-number
{
font-size
:
9px
;
letter-spacing
:
.09em
;
color
:
var
(
--subtle
)}
.state-badge
{
font-size
:
10px
;
color
:
var
(
--muted
);
margin-left
:
auto
}
.flow-grid
{
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1
fr
)
85px
minmax
(
0
,
1
fr
)
65px
minmax
(
0
,
1
fr
);
align-items
:
stretch
}
.system
{
background
:
var
(
--paper
);
border
:
1px
solid
var
(
--line
);
border-radius
:
5px
;
min-width
:
0
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
text-align
:
center
;
padding
:
19px
7px
;
min-height
:
125px
}
.system.saas
{
background
:
var
(
--teal-soft
);
border-color
:
var
(
--teal-line
)}
.system-kicker
{
font-size
:
10px
;
color
:
var
(
--subtle
);
letter-spacing
:
.08em
;
margin-bottom
:
5px
}
.system
strong
{
font-size
:
17px
;
color
:
var
(
--heading
);
font-weight
:
650
;
line-height
:
1.5
}
.system
p
{
font-size
:
10px
!important
;
line-height
:
1.8
;
color
:
var
(
--muted
);
margin
:
8px
0
0
!important
}
.connector
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
text-align
:
center
;
color
:
var
(
--muted
);
gap
:
5px
;
padding
:
2px
5px
;
font-size
:
10px
;
line-height
:
1.65
}
.connector
.icon
{
height
:
20px
;
width
:
34px
;
color
:
var
(
--teal
)}
.connector
small
{
font-size
:
9px
;
color
:
var
(
--amber
)}
.flow-top
{
width
:
calc
((
100%
-
150px
)/
3
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
3px
;
margin-bottom
:
3px
}
.capture
{
display
:
flex
;
align-items
:
center
;
gap
:
5px
;
font-size
:
10px
;
line-height
:
1.65
;
white-space
:
nowrap
;
color
:
var
(
--muted
)}
.capture
.icon
{
width
:
14px
;
height
:
14px
}
.capture-arrow
{
font-size
:
20px
;
color
:
var
(
--subtle
);
line-height
:
1.1
}
.flow-bottom
{
width
:
calc
((
100%
-
150px
)/
3
);
display
:
flex
;
justify-content
:
center
}
.storage
{
position
:
relative
;
padding-top
:
23px
;
display
:
flex
;
align-items
:
center
;
gap
:
7px
;
color
:
var
(
--muted
);
font-size
:
13px
}
.storage
.icon
{
width
:
16px
;
height
:
16px
}
.storage
small
{
font-size
:
10px
;
margin-left
:
4px
}
.storage-line
{
height
:
17px
;
border-left
:
1px
solid
var
(
--subtle
);
position
:
absolute
;
top
:
0
;
left
:
50%
}
.storage-line
:after
{
content
:
""
;
position
:
absolute
;
bottom
:
0
;
left
:
-3px
;
border
:
solid
var
(
--subtle
);
border-width
:
0
1px
1px
0
;
padding
:
2px
;
transform
:
rotate
(
45deg
)}
.view-path
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
7px
;
padding
:
18px
17px
;
background
:
var
(
--blue-soft
);
border-radius
:
4px
;
font-size
:
12px
;
font-weight
:
550
;
color
:
var
(
--blue
);
margin
:
16px
0
}
.view-path
>
span
{
display
:
flex
;
gap
:
7px
;
align-items
:
center
}
.view-path
>
.icon
{
width
:
17px
;
height
:
17px
;
opacity
:
.7
}
.view-path
>
span
>
.icon
{
width
:
17px
;
height
:
17px
}
.table-wrap
{
margin
:
23px
0
;
overflow
:
hidden
;
border
:
1px
solid
var
(
--line
);
border-radius
:
4px
}
table
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
14px
;
line-height
:
1.85
;
text-align
:
left
}
th
{
font-weight
:
600
;
font-size
:
12px
;
color
:
var
(
--muted
);
background
:
var
(
--soft
);
padding
:
12px
16px
;
border-bottom
:
1px
solid
var
(
--line
)}
td
{
padding
:
15px
16px
;
border-bottom
:
1px
solid
var
(
--line
);
vertical-align
:
top
}
tbody
tr
:last-child
td
{
border-bottom
:
0
}
td
:first-child
{
font-weight
:
550
;
color
:
var
(
--heading
);
width
:
37%
}
#meyer
td
:first-child
{
width
:
31%
}
#meyer
td
:nth-child
(
2
)
{
width
:
26%
}
#review
td
:first-child
{
width
:
23%
}
tbody
tr
:hover
{
background
:
var
(
--soft
)}
.responsibilities
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
20px
;
list-style
:
none
;
padding
:
0
!important
;
margin
:
22px
0
!important
}
.responsibilities
li
{
position
:
relative
;
margin
:
0
;
padding
:
22px
22px
20px
;
background
:
var
(
--teal-soft
);
border
:
1px
solid
var
(
--teal-line
);
border-radius
:
4px
;
font-size
:
14px
;
line-height
:
1.95
}
.responsibilities
li
>
strong
{
display
:
block
;
font-size
:
18px
;
margin
:
12px
0
5px
}
.responsibility-icon
{
display
:
inline-flex
;
color
:
var
(
--teal
)}
.responsibility-icon
.icon
{
width
:
24px
;
height
:
24px
}
.future
{
margin-top
:
27px
;
border-top
:
1px
solid
var
(
--line
);
border-bottom
:
1px
solid
var
(
--line
)}
summary
{
list-style
:
none
;
cursor
:
pointer
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
15px
;
padding
:
20px
0
;
font-size
:
15px
;
font-weight
:
550
;
color
:
var
(
--heading
)}
summary
::-webkit-details-marker
{
display
:
none
}
summary
.note-label
{
font-size
:
10px
;
margin-bottom
:
4px
;
color
:
var
(
--subtle
)}
summary
>
.icon
{
width
:
17px
;
height
:
17px
;
transition
:
transform
.18s
}
details
[
open
]
summary
>
.icon
{
transform
:
rotate
(
90deg
)}
.detail-body
{
font-size
:
14px
;
color
:
var
(
--muted
);
padding-bottom
:
17px
}
.detail-body
>
p
:first-child
{
margin-top
:
0
}
.detail-body
>
ul
:last-child
{
margin-bottom
:
0
}
.open-questions
{
list-style
:
none
;
padding
:
0
!important
;
display
:
grid
;
gap
:
12px
;
margin
:
24px
0
!important
}
.open-questions
li
{
position
:
relative
;
margin
:
0
;
padding
:
19px
22px
19px
62px
;
background
:
var
(
--amber-soft
);
border
:
1px
solid
var
(
--amber-line
);
border-radius
:
4px
;
font-size
:
14px
;
line-height
:
1.9
}
.open-questions
strong
{
display
:
block
;
font-size
:
17px
;
margin-bottom
:
4px
}
.question-no
{
position
:
absolute
;
left
:
20px
;
top
:
22px
;
font-size
:
14px
;
font-family
:
Georgia
,
serif
;
color
:
var
(
--amber
)}
.sources-section
{
padding
:
29px
0
18px
;
border-top
:
1px
solid
var
(
--line
);
scroll-margin-top
:
0
}
.sources-section
>
details
>
summary
{
padding
:
5px
0
16px
}
.sources-title
{
display
:
block
;
font-size
:
20px
;
line-height
:
1.6
}
.sources-intro
{
display
:
block
;
font-size
:
12px
;
color
:
var
(
--subtle
);
margin-top
:
4px
}
.sources-content
{
font-size
:
13px
;
color
:
var
(
--muted
);
line-height
:
1.95
}
.sources-content
p
{
margin
:
14px
0
}
.sources-content
ul
{
padding-left
:
18px
}
.sources-content
li
{
margin
:
12px
0
;
padding-left
:
3px
}
.sources-content
a
{
text-decoration
:
underline
;
text-decoration-color
:
var
(
--teal-line
);
text-underline-offset
:
4px
}
.document-footer
{
border-top
:
1px
solid
var
(
--line
);
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
20px
;
padding-top
:
22px
;
font-size
:
10px
;
color
:
var
(
--subtle
);
letter-spacing
:
.02em
}
.document-footer
a
{
white-space
:
nowrap
}
.toast
{
position
:
fixed
;
bottom
:
24px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
z-index
:
60
;
background
:
var
(
--heading
);
color
:
var
(
--paper
);
padding
:
9px
17px
;
border-radius
:
6px
;
font-size
:
12px
;
box-shadow
:
0
5px
20px
#0001
;
max-width
:
90vw
;
text-align
:
center
;
pointer-events
:
none
}
.toast
[
hidden
]
{
display
:
none
}
.source-note
{
font-size
:
10px
!important
;
color
:
var
(
--subtle
)}
:root
[
data-size
=
"large"
]
.prose
{
font-size
:
18px
}
:root
[
data-size
=
"large"
]
.prose
table
{
font-size
:
16px
}
:root
[
data-size
=
"large"
]
.prose
.own-business
,
:root
[
data-size
=
"large"
]
.prose
.continuity
,
:root
[
data-size
=
"large"
]
.prose
.consequence
,
:root
[
data-size
=
"large"
]
.prose
.responsibilities
li
{
font-size
:
16px
}
@media
(
min-width
:
1500px
){
.shell
{
gap
:
52px
;
grid-template-columns
:
218px
minmax
(
0
,
1
fr
)}
.document
{
padding-left
:
68px
;
padding-right
:
68px
}}
@media
(
max-width
:
1180px
){
.shell
{
padding
:
26px
24px
50px
;
grid-template-columns
:
170px
minmax
(
0
,
1
fr
);
gap
:
25px
}
.document
{
padding
:
45px
35px
25px
}
.topbar-inner
{
padding
:
0
24px
}
.sidebar
{
top
:
95px
}
.toc
a
{
padding-left
:
8px
;
gap
:
8px
;
font-size
:
12px
}
.toc
small
{
font-size
:
10px
}
.flow-grid
{
grid-template-columns
:
minmax
(
0
,
1
fr
)
70px
minmax
(
0
,
1
fr
)
55px
minmax
(
0
,
1
fr
)}
.flow-top
,
.flow-bottom
{
width
:
calc
((
100%
-
125px
)/
3
)}
.system
strong
{
font-size
:
15px
}
.system
p
{
font-size
:
9px
!important
}
.flow-figure
{
padding
:
18px
16px
}
.capture
{
font-size
:
9px
}
.boundary
{
gap
:
18px
}
.boundary
>
div
+
div
{
padding-left
:
18px
}
.boundary
h2
{
font-size
:
21px
}
h1
{
font-size
:
46px
}
h2
{
font-size
:
25px
}}
@media
(
max-width
:
960px
){
.shell
{
display
:
block
;
padding
:
0
24px
45px
}
.sidebar
{
position
:
sticky
;
top
:
70px
;
z-index
:
20
;
max-height
:
none
;
background
:
var
(
--bg
);
padding
:
10px
0
;
border-bottom
:
1px
solid
var
(
--line
);
margin-bottom
:
25px
;
overflow
:
visible
}
.toc-label
,
.rail-note
{
display
:
none
}
.toc
{
display
:
flex
;
overflow
:
auto
;
scrollbar-width
:
none
;
gap
:
5px
}
.toc
::-webkit-scrollbar
{
display
:
none
}
.toc
a
{
flex
:
none
;
align-items
:
center
;
padding
:
8px
13px
;
border
:
0
;
border-bottom
:
2px
solid
transparent
;
border-radius
:
5px
;
font-size
:
12px
;
white-space
:
nowrap
;
gap
:
7px
}
.toc
a
.active
{
border-bottom-color
:
var
(
--teal
)}
.toc
small
{
display
:
none
}
.toc-number
{
padding
:
0
;
font-size
:
9px
}
.document
{
padding
:
45px
48px
25px
;
max-width
:
830px
;
margin
:
auto
}
.document
:before
{
left
:
48px
}
html
{
scroll-padding-top
:
145px
}
.chapter
,
.sources-section
{
scroll-margin-top
:
0
}
.flow-grid
{
grid-template-columns
:
minmax
(
0
,
1
fr
)
85px
minmax
(
0
,
1
fr
)
65px
minmax
(
0
,
1
fr
)}
.flow-top
,
.flow-bottom
{
width
:
calc
((
100%
-
150px
)/
3
)}
.system
strong
{
font-size
:
17px
}
.system
p
{
font-size
:
10px
!important
}
.capture
{
font-size
:
10px
}
.chapter-head
{
gap
:
17px
}}
@media
(
max-width
:
640px
){
body
{
font-size
:
15px
;
line-height
:
1.95
}
.topbar
{
height
:
59px
}
.topbar-inner
{
padding
:
0
16px
;
gap
:
6px
}
.doc-mark
{
font-size
:
11px
;
gap
:
7px
}
.mark-icon
{
width
:
27px
;
height
:
27px
}
.mark-icon
.icon
{
width
:
17px
;
height
:
17px
}
.version
,
.print-label
{
display
:
none
}
.toolbar
{
gap
:
0
}
.tool
{
min-width
:
33px
;
min-height
:
34px
;
padding
:
6px
}
.print-button
{
margin-left
:
2px
;
border
:
0
}
.shell
{
padding
:
0
13px
22px
}
.sidebar
{
top
:
59px
;
padding
:
8px
0
;
margin-bottom
:
15px
}
.toc
a
{
font-size
:
11px
;
padding
:
7px
11px
}
.document
{
padding
:
34px
22px
22px
;
border-radius
:
3px
}
.document
:before
{
left
:
22px
;
width
:
46px
}
.eyebrow
{
font-size
:
10px
;
gap
:
6px
;
margin-bottom
:
20px
;
letter-spacing
:
.025em
}
h1
{
font-size
:
37px
;
line-height
:
1.35
}
.hero-deck
{
font-size
:
17px
;
margin-top
:
13px
}
.meta
{
font-size
:
10px
;
gap
:
10px
;
margin-top
:
20px
}
.hero-scope
{
font-size
:
12px
;
margin-top
:
13px
}
.hero
{
padding-bottom
:
29px
}
.boundary
{
margin-top
:
28px
;
gap
:
11px
;
padding
:
20px
0
}
.boundary
>
div
+
div
{
padding-left
:
11px
}
.boundary
.role-name
{
font-size
:
10px
}
.boundary
h2
{
font-size
:
17px
;
line-height
:
1.7
;
margin-top
:
5px
}
.boundary
p
{
font-size
:
10px
}
.core-boundary
{
font-size
:
10px
}
.principle
{
font-size
:
13px
;
padding-top
:
22px
;
line-height
:
1.95
}
.principle
strong
{
font-size
:
18px
}
.chapter
{
padding
:
28px
0
30px
;
scroll-margin-top
:
0
}
.chapter-head
{
gap
:
12px
;
margin-bottom
:
21px
}
.chapter-num
{
font-size
:
30px
;
min-width
:
34px
;
padding-top
:
13px
}
h2
{
font-size
:
22px
;
line-height
:
1.6
}
.chapter-kicker
{
font-size
:
9px
}
.prose
p
{
margin
:
15px
0
}
h3
{
font-size
:
18px
}
.step-heading
{
font-size
:
15px
}
.flow-figure
{
padding
:
16px
;
margin
:
17px
-3px
21px
}
.flow-figure
figcaption
{
font-size
:
11px
;
gap
:
6px
;
margin-bottom
:
20px
}
.figure-number
{
font-size
:
8px
}
.state-badge
{
display
:
block
;
flex-basis
:
100%
;
margin-left
:
0
;
font-size
:
10px
}
.flow-grid
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
stretch
;
gap
:
0
}
.flow-top
{
width
:
100%
;
margin-bottom
:
7px
}
.capture
{
font-size
:
11px
}
.system
{
min-height
:
101px
;
padding
:
16px
10px
}
.system
strong
{
font-size
:
19px
}
.system
p
{
font-size
:
11px
!important
;
margin-top
:
5px
!important
}
.system-kicker
{
font-size
:
10px
;
margin-bottom
:
2px
}
.connector
{
padding
:
12px
0
;
min-height
:
78px
;
display
:
grid
;
grid-template-columns
:
1
fr
22px
1
fr
;
gap
:
7px
;
text-align
:
right
;
font-size
:
11px
}
.connector
.icon
{
transform
:
rotate
(
90deg
);
width
:
23px
;
height
:
23px
}
.connector
small
{
font-size
:
9px
;
text-align
:
left
;
max-width
:
70px
}
.connector
:not
(
:has
(
small
))
:after
{
content
:
""
}
.connector
br
{
display
:
none
}
.flow-bottom
{
width
:
100%
;
justify-content
:
flex-start
;
margin-top
:
14px
}
.storage
{
padding
:
0
;
font-size
:
11px
}
.storage-line
{
display
:
none
}
.storage
:before
{
content
:
"美亚 →"
;
font-size
:
10px
;
color
:
var
(
--subtle
);
margin-right
:
2px
}
.storage
small
{
font-size
:
10px
}
.view-path
{
flex-direction
:
column
;
align-items
:
flex-start
;
gap
:
8px
;
padding
:
18px
20px
;
font-size
:
12px
}
.view-path
>
.icon
{
transform
:
rotate
(
90deg
);
margin-left
:
2px
}
.consequence
{
padding
:
17px
18px
;
font-size
:
14px
}
.requirement
{
padding
:
17px
19px
;
font-size
:
19px
;
margin
:
19px
0
}
.emphasis
{
font-size
:
15px
;
padding-left
:
15px
}
.table-wrap
{
border
:
0
;
border-top
:
1px
solid
var
(
--line
);
border-radius
:
0
;
margin
:
20px
0
}
table
,
tbody
,
tr
,
td
{
display
:
block
}
thead
{
display
:
none
}
tr
{
padding
:
15px
0
;
border-bottom
:
1px
solid
var
(
--line
)}
td
{
padding
:
4px
0
;
border
:
0
;
font-size
:
13px
;
line-height
:
1.9
;
width
:
auto
!important
}
td
:first-child
{
font-size
:
14px
;
font-weight
:
650
;
margin-bottom
:
5px
}
td
:not
(
:first-child
)
:before
{
content
:
attr
(
data-label
);
color
:
var
(
--subtle
);
font-size
:
10px
;
display
:
block
;
letter-spacing
:
.025em
}
#meyer
td
:nth-child
(
2
)
{
width
:
auto
}
tbody
tr
:hover
{
background
:
transparent
}
.own-business
{
font-size
:
13px
;
padding
:
18px
}
.continuity
{
font-size
:
14px
;
padding-left
:
15px
}
.responsibilities
{
grid-template-columns
:
1
fr
;
gap
:
12px
}
.responsibilities
li
{
font-size
:
14px
;
padding
:
20px
}
.responsibilities
li
>
strong
{
font-size
:
17px
;
margin-top
:
9px
}
.open-questions
li
{
padding
:
16px
17px
16px
49px
;
font-size
:
13px
}
.question-no
{
left
:
16px
;
top
:
19px
;
font-size
:
12px
}
.open-questions
strong
{
font-size
:
16px
}
.sources-section
{
scroll-margin-top
:
0
}
.sources-content
{
font-size
:
12px
;
overflow-wrap
:
anywhere
}
.document-footer
{
font-size
:
9px
;
gap
:
12px
;
align-items
:
flex-start
}
.document-footer
span
{
max-width
:
220px
}
.source-note
{
font-size
:
9px
!important
}
html
{
scroll-padding-top
:
126px
}}
@media
(
max-width
:
640px
){
.boundary
{
grid-template-columns
:
1
fr
;
gap
:
13px
;
padding
:
19px
0
}
.boundary
>
div
{
display
:
grid
;
grid-template-columns
:
92px
1
fr
;
column-gap
:
12px
;
align-items
:
baseline
}
.boundary
>
div
+
div
{
border-left
:
0
;
border-top
:
1px
solid
var
(
--line
);
padding
:
12px
0
0
}
.boundary
.role-name
{
grid-row
:
1
/
3
;
font-size
:
11px
}
.boundary
h2
{
font-size
:
18px
;
margin
:
0
;
line-height
:
1.5
}
.boundary
p
{
grid-column
:
2
;
margin-top
:
3px
;
font-size
:
11px
}
.principle
strong
{
margin-bottom
:
10px
}}
@media
(
prefers-reduced-motion
:
reduce
){
html
{
scroll-behavior
:
auto
}
*,*
:before
,*
:after
{
transition
:
none
!important
;
animation
:
none
!important
}}
@media
print
{
html
{
scroll-behavior
:
auto
;
color-scheme
:
light
!important
}
:root
,
:root
[
data-theme
=
"dark"
],
:root:not
([
data-theme
=
"light"
])
{
--bg
:
#fff
;
--paper
:
#fff
;
--ink
:
#25322e
;
--heading
:
#172e2b
;
--muted
:
#53645c
;
--subtle
:
#65736c
;
--line
:
#d5dbd3
;
--soft
:
#f4f6f0
;
--teal
:
#17665b
;
--teal-soft
:
#edf4ed
;
--teal-line
:
#bad3c4
;
--amber
:
#80591f
;
--amber-soft
:
#faf4e8
;
--amber-line
:
#e1cfaa
;
--blue
:
#355d80
;
--blue-soft
:
#eef3f7
}
body
{
background
:
white
;
font-size
:
10pt
;
line-height
:
1.75
;
-webkit-print-color-adjust
:
exact
;
print-color-adjust
:
exact
}
.topbar
,
.sidebar
,
.page-progress
,
.toast
,
.skip-link
{
display
:
none
!important
}
.shell
{
display
:
block
;
max-width
:
none
;
margin
:
0
;
padding
:
0
}
.document
{
padding
:
0
;
border
:
0
;
border-radius
:
0
;
box-shadow
:
none
;
max-width
:
none
}
.document
:before
{
display
:
none
}
.hero
{
padding
:
0
0
22px
}
h1
{
font-size
:
32pt
}
.hero-deck
{
font-size
:
15pt
}
.eyebrow
{
font-size
:
9pt
}
.meta
,
.hero-scope
{
font-size
:
8pt
}
.boundary
{
margin-top
:
20px
;
padding
:
15px
0
;
break-inside
:
avoid
}
.boundary
h2
{
font-size
:
16pt
}
.boundary
.role-name
{
font-size
:
9pt
}
.boundary
p
{
font-size
:
8pt
}
.principle
{
font-size
:
9pt
}
.principle
strong
{
font-size
:
13pt
}
.chapter
{
padding
:
24px
0
;
break-before
:
auto
}
.chapter-head
{
break-after
:
avoid
;
margin-bottom
:
15px
}
.chapter-num
{
font-size
:
28pt
}
h2
{
font-size
:
18pt
}
h3
{
font-size
:
13pt
;
break-after
:
avoid
}
.prose
p
{
margin
:
12px
0
;
orphans
:
3
;
widows
:
3
}
.prose
ul
,
.prose
ol
{
margin
:
12px
0
}
.flow-figure
{
break-inside
:
avoid
;
background-image
:
none
;
padding
:
18px
}
.system
{
min-height
:
100px
}
.flow-grid
{
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1
fr
)
85px
minmax
(
0
,
1
fr
)
65px
minmax
(
0
,
1
fr
)}
.flow-top
,
.flow-bottom
{
width
:
calc
((
100%
-
150px
)/
3
)}
.system
strong
{
font-size
:
12pt
}
.system
p
{
font-size
:
7pt
!important
}
.table-wrap
{
overflow
:
visible
;
break-inside
:
avoid
}
table
{
font-size
:
9pt
}
th
{
font-size
:
8pt
}
td
,
th
{
padding
:
9px
12px
}
tr
{
break-inside
:
avoid
}
thead
{
display
:
table-header-group
}
.consequence
,
.requirement
,
.own-business
,
.continuity
,
.responsibilities
li
,
.open-questions
li
{
break-inside
:
avoid
}
.consequence
,
.emphasis
,
.continuity
{
font-size
:
10pt
}
.requirement
{
font-size
:
14pt
}
.own-business
,
.footnote-callout
{
font-size
:
9pt
}
.responsibilities
li
{
font-size
:
9pt
}
.responsibilities
li
>
strong
{
font-size
:
12pt
}
.future
{
break-inside
:
avoid
}
details
>
.detail-body
,
details
>
.sources-content
{
display
:
block
!important
}
details
::details-content
{
content-visibility
:
visible
!important
;
height
:
auto
!important
}
summary
.icon
{
display
:
none
}
.open-questions
li
{
font-size
:
9pt
}
.open-questions
strong
{
font-size
:
12pt
}
.sources-content
{
font-size
:
8pt
}
.sources-section
{
break-inside
:
avoid
}
.sources-content
a
{
color
:
var
(
--ink
)}
.document-footer
{
font-size
:
7pt
;
margin-top
:
15px
}
.document-footer
a
{
display
:
none
}
a
{
text-decoration
:
none
}
.step-heading
,
.figure-intro
,
.prose
>
p
:first-child
,
.prose
>
p
:has
(+
.table-wrap
),
.prose
>
p
:has
(+
blockquote
),
.prose
>
p
:has
(+
.responsibilities
),
.prose
>
p
:has
(+
.open-questions
)
{
break-after
:
avoid
}
.sources-title
{
font-size
:
13pt
}
@page
{
size
:
A4
;
margin
:
17mm
16mm
18mm
}}
</style>
</head>
<body>
<aside
role=
"note"
style=
"padding:12px 20px;background:#fff4da;color:#594100;border-bottom:1px solid #e9cf8d;font:14px/1.7 sans-serif"
>
历史评审稿,2026-10-22 归档。事件推送方案未被采用;现行实现按诊所分页轮询并批量查询分析。
<a
href=
"../../../apps/pac-service/src/integrations/meyer/README.md"
>
查看当前采集说明
</a></aside>
<a
class=
"skip-link"
href=
"#document"
>
跳到正文
</a><div
class=
"page-progress"
aria-hidden=
"true"
></div>
<header
class=
"topbar"
><div
class=
"topbar-inner"
><a
class=
"doc-mark"
href=
"#document"
><span
class=
"mark-icon"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m12 3 10 5-10 5L2 8l10-5ZM2 12l10 5 10-5M2 16l10 5 10-5"
/></svg></span><span>
影像与分析 · 方案设计
</span></a><div
class=
"toolbar"
><span
class=
"version"
>
内部评审 · v0.2
</span><button
class=
"tool"
id=
"font-button"
aria-label=
"切换较大字号"
aria-pressed=
"false"
title=
"切换字号"
><span
class=
"size-icon"
aria-hidden=
"true"
>
Aa
</span></button><button
class=
"tool"
id=
"theme-button"
aria-label=
"切换深色模式"
title=
"切换深浅色"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><circle
cx=
"12"
cy=
"12"
r=
"4"
/><path
d=
"M12 2v2m0 16v2M2 12h2m16 0h2M5 5l1.5 1.5m11 11L19 19M5 19l1.5-1.5m11-11L19 5"
/></svg></button><button
class=
"tool print-button"
id=
"print-button"
aria-label=
"打印或保存为 PDF"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M6 9V3h12v6M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
/><path
d=
"M6 14h12v8H6zM18 12h.01"
/></svg><span
class=
"print-label"
>
打印 / PDF
</span></button></div></div></header>
<div
class=
"shell"
>
<aside
class=
"sidebar"
aria-label=
"文档目录"
><p
class=
"toc-label"
>
阅读导航
</p><nav
class=
"toc"
><a
href=
"#relationship"
class=
"active"
aria-current=
"location"
><span
class=
"toc-number"
>
01
</span><span>
整体关系
<small>
数据供给与原片查看
</small></span></a><a
href=
"#meyer"
><span
class=
"toc-number"
>
02
</span><span>
美亚 PACS
<small>
让变化可获取
</small></span></a><a
href=
"#saas"
><span
class=
"toc-number"
>
03
</span><span>
FridaySaaS
<small>
留业务记录,统一来源
</small></span></a><a
href=
"#pac"
><span
class=
"toc-number"
>
04
</span><span>
PAC
<small>
消费信号,打开影像
</small></span></a><a
href=
"#review"
><span
class=
"toc-number"
>
05
</span><span>
评审与确认
<small>
方向与待确认能力
</small></span></a><a
href=
"#sources"
><span
class=
"toc-number"
>
↗
</span><span>
资料依据
<small>
范围与原始出处
</small></span></a></nav><div
class=
"rail-note"
><b>
先确定边界,再细化实现
</b>
本稿讨论设计方向。接口、存储和开发任务在方案采纳后展开。
<div
class=
"reading-status"
><span>
阅读进度
</span><span
id=
"progress-value"
>
0%
</span></div><div
class=
"reading-track"
aria-hidden=
"true"
><span></span></div></div></aside>
<main
class=
"document"
id=
"document"
>
<header
class=
"hero"
><p
class=
"eyebrow"
>
美亚 PACS
<span
class=
"sep"
>
×
</span>
FridaySaaS
<span
class=
"sep"
>
×
</span>
PAC
</p><h1><span>
影像与分析数据
</span><span>
方案设计
</span></h1><p
class=
"hero-deck"
>
让影像资产、业务归属与诊断信号各有承接。
</p><div
class=
"meta"
><span
class=
"draft"
>
内部评审稿
</span><span>
v0.2
</span><time
datetime=
"2026-10-16"
>
2026.10.16
</time></div><p
class=
"hero-scope"
>
本轮先确认设计方向与系统边界;字段、表结构、调度周期和开发任务在方案采纳后细化。
</p>
<div
class=
"boundary"
aria-label=
"建议的三方职责"
><div><span
class=
"role-name"
>
美亚 PACS
</span><h2>
管理影像资产
</h2><p>
影像与分析 · 原片访问
</p></div><div><span
class=
"role-name"
>
FridaySaaS
</span><h2>
管理业务归属
</h2><p>
关系校验 · 留存与同步
</p></div><div><span
class=
"role-name"
>
PAC
</span><h2>
消费宿主事实
</h2><p>
诊断信号 · 画像与召回
</p></div></div><p
class=
"core-boundary"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m5 12 4 4L19 6"
/></svg>
PAC 核心不直接接美亚。
</p><div
class=
"principle"
><p><span
class=
"note-label"
>
判断依据
</span><strong>
三个系统可以保存和使用同一份患者与影像关系,但应明确各自数据的权威来源,并让关联及其变更沿统一链路传递。
</strong>
FridaySaaS 提供业务患者身份,美亚维护影像资产及其患者映射;SaaS 将两者对应关系校验、留存后提供给 PAC,PAC 据此形成事实和诊断信号。
</p>
</div></header>
<section
class=
"chapter"
id=
"relationship"
aria-labelledby=
"heading-relationship"
><header
class=
"chapter-head"
><span
class=
"chapter-num"
aria-hidden=
"true"
>
01
</span><div><p
class=
"chapter-kicker"
>
RELATIONSHIP
</p><h2
id=
"heading-relationship"
>
整体关系:数据供给与查看原片分开
</h2></div></header><div
class=
"prose"
><p
class=
"step-heading"
><strong>
① 关联基础:先明确影像属于谁
</strong></p>
<p>
FridaySaaS 向美亚同步患者、诊所和就诊信息,美亚据此关联影像;SaaS 校验并留存对应关系,再向 PAC 提供。
</p>
<p
class=
"step-heading"
><strong>
② 数据供给:同步影像信息与分析结果
</strong></p>
<p
class=
"figure-intro"
>
以下以启用美亚新影像为例,表示目标链路:
</p>
<figure
class=
"flow-figure"
aria-labelledby=
"flow-caption"
>
<figcaption
id=
"flow-caption"
><span
class=
"figure-number"
>
FIG. 01
</span><strong>
影像与分析的数据供给
</strong><span
class=
"state-badge"
>
目标链路 · 新影像启用时
</span></figcaption>
<div
class=
"flow-top"
><span
class=
"capture"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m12 3 10 5-10 5L2 8l10-5ZM2 12l10 5 10-5M2 16l10 5 10-5"
></path></svg>
诊所设备与前置机
</span><span
class=
"capture-arrow"
aria-hidden=
"true"
>
↓
</span></div>
<div
class=
"flow-grid"
>
<div
class=
"system meyer"
><span
class=
"system-kicker"
>
影像资产
</span><strong>
美亚 PACS
</strong><p>
采集接入 · 影像与分析
</p></div>
<div
class=
"connector"
><span>
影像信息
<br>
分析结果及变更
</span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M4 12h16m-6-6 6 6-6 6"
></path></svg><small>
获取方式待确认
</small></div>
<div
class=
"system saas"
><span
class=
"system-kicker"
>
业务归属
</span><strong>
FridaySaaS
</strong><p>
校验归属 · 留存 · 同步
</p></div>
<div
class=
"connector"
><span>
宿主事实
</span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M4 12h16m-6-6 6 6-6 6"
></path></svg></div>
<div
class=
"system pac"
><span
class=
"system-kicker"
>
信号应用
</span><strong>
PAC
</strong><p>
诊断信号 · 画像与召回
</p></div>
</div>
<div
class=
"flow-bottom"
><div
class=
"storage"
><span
class=
"storage-line"
aria-hidden=
"true"
></span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m12 3 10 5-10 5L2 8l10-5ZM2 12l10 5 10-5M2 16l10 5 10-5"
></path></svg><span>
OSS
<small>
原片存储
</small></span></div></div>
</figure>
<p>
原片由美亚管理,SaaS 向 PAC 提供影像业务信息与分析结果。美亚到 SaaS 如何获取变更,仍需确认。
</p>
<p>
新老影像功能可以保留,实际使用时择一启用;选用旧影像时,也由 SaaS 统一向 PAC 提供数据。
</p>
<p
class=
"step-heading"
><strong>
③ 查看原片:用户按需打开宿主入口
</strong></p>
<div
class=
"view-path"
role=
"group"
aria-label=
"查看原片路径"
><span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m10 13 4-4m-6 6-2 2a4 4 0 0 1-6-6l5-5a4 4 0 0 1 6 0m2 3 2-2a4 4 0 0 1 6 6l-5 5a4 4 0 0 1-6 0"
transform=
"translate(1 1) scale(.9)"
></path></svg>
PAC 宿主槽位
</span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M4 12h16m-6-6 6 6-6 6"
></path></svg><span>
FridaySaaS 影像页面
</span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M4 12h16m-6-6 6 6-6 6"
></path></svg><span>
当前选用的影像系统
</span></div>
<p>
SaaS 负责权限和入口;启用新影像时,由美亚提供阅片服务。
<strong>
数据同步持续进行,查看原片由用户按需发起。
</strong></p>
</div></section>
<section
class=
"chapter"
id=
"meyer"
aria-labelledby=
"heading-meyer"
><header
class=
"chapter-head"
><span
class=
"chapter-num"
aria-hidden=
"true"
>
02
</span><div><p
class=
"chapter-kicker"
>
MEYER PACS
</p><h2
id=
"heading-meyer"
>
美亚:最重要的承诺是“变化可获取”
</h2></div></header><div
class=
"prose"
><p>
美亚负责采集接入、原片存储、影像关联、阅片,以及其提供的分析。对外不仅要能查询当前数据,还要让 SaaS 知道数据发生了什么变化。
</p>
<p
class=
"consequence"
><span
class=
"note-label"
>
只发现新增,会漏掉什么
</span><strong>
如果只能查新影像列表,SaaS 可以知道“多了一张片”,却未必知道这张片后来分析完成、被改绑到另一位患者,或被撤销。下游就可能继续使用过期甚至归属错误的依据。
</strong></p>
<p>
因此,对美亚真正要提的要求是:
</p>
<blockquote
class=
"requirement"
>
<p>
提供可追溯、可补拉的影像及分析变更机制,包含新增、修改、撤销和归属变化。
</p>
</blockquote>
<div
class=
"table-wrap"
><table>
<thead>
<tr>
<th
scope=
"col"
>
美亚提供的能力
</th>
<th
scope=
"col"
>
FridaySaaS 如何获取
</th>
<th
scope=
"col"
>
意味着什么
</th>
</tr>
</thead>
<tbody><tr>
<td
data-label=
"美亚提供的能力"
>
有事件通知,并能补查变更
</td>
<td
data-label=
"FridaySaaS 如何获取"
>
通知后同步,遗漏时补拉
</td>
<td
data-label=
"意味着什么"
>
及时性较好,也能恢复遗漏
</td>
</tr>
<tr>
<td
data-label=
"美亚提供的能力"
>
没有通知,有完整变更查询
</td>
<td
data-label=
"FridaySaaS 如何获取"
>
定时拉取
</td>
<td
data-label=
"意味着什么"
>
可以可靠同步,延迟取决于拉取周期
</td>
</tr>
<tr>
<td
data-label=
"美亚提供的能力"
>
只有入库时间列表、逐张查询
</td>
<td
data-label=
"FridaySaaS 如何获取"
>
定时发现新片,反复查询分析
</td>
<td
data-label=
"意味着什么"
>
能做部分同步,但后续修订、改绑、撤销的完整性没有保障
</td>
</tr>
</tbody></table></div>
</div></section>
<section
class=
"chapter"
id=
"saas"
aria-labelledby=
"heading-saas"
><header
class=
"chapter-head"
><span
class=
"chapter-num"
aria-hidden=
"true"
>
03
</span><div><p
class=
"chapter-kicker"
>
FRIDAY SAAS
</p><h2
id=
"heading-saas"
>
FridaySaaS:留业务记录,统一新老来源
</h2></div></header><div
class=
"prose"
><h3>
3.1 为什么需要留新影像数据
</h3>
<p
class=
"emphasis"
><strong>
如果 SaaS 只在用户打开页面时代理查询美亚,它承担的是展示功能,无法承担稳定的下游数据供给。
</strong>
没人打开页面时,PAC 仍需要知道患者有了新的影像或分析。
</p>
<p>
既然 PAC 从 SaaS 接收数据,SaaS 就应保存一份能够重试、重推、核对的影像业务记录:
</p>
<div
class=
"table-wrap"
><table>
<thead>
<tr>
<th
scope=
"col"
>
应保存的信息
</th>
<th
scope=
"col"
>
用途
</th>
</tr>
</thead>
<tbody><tr>
<td
data-label=
"应保存的信息"
>
患者、租户与影像采集诊所
</td>
<td
data-label=
"用途"
>
明确业务归属;采集诊所不随患者后续转店而改变
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
影像来源、影像与检查标识
</td>
<td
data-label=
"用途"
>
稳定识别、去重和追溯
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
类型、拍摄时间、绑定及有效状态
</td>
<td
data-label=
"用途"
>
解释这份影像的业务含义
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
稳定文件引用,如 OSS ID
</td>
<td
data-label=
"用途"
>
后续获取原片;临时链接过期后仍可定位
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
分析结果、来源及版本
</td>
<td
data-label=
"用途"
>
保留当时依据,支持补推和重放
</td>
</tr>
<tr>
<td
data-label=
"应保存的信息"
>
同步进度与失败记录
</td>
<td
data-label=
"用途"
>
知道哪些已交付、哪些还需要补齐
</td>
</tr>
</tbody></table></div>
<p>
原片继续由美亚管理。SaaS 保存索引和结果,不重复建设影像存储;临时签名 URL 不能作为永久地址。
</p>
<p
class=
"continuity"
><strong>
这样即使美亚暂时不可用,SaaS 和 PAC 仍可查询已留存的影像信息和分析结果;原片访问和最新变更同步则可能受影响。
</strong>
分析结果会影响后续业务判断,值得留存,而不应仅在查看页面时临时取一次。
</p>
<p>
美亚接入、获取变更和归属校验由 SaaS 对接层负责;open-connector 从 SaaS 已保存的业务记录向 PAC 推送。供应商协议与下游数据传输各自负责自己的部分。
</p>
<p
class=
"own-business"
><span
class=
"note-label"
>
SaaS 自身业务同样受益
</span>
即使不接 PAC,留存业务记录也能支撑 SaaS 自身业务:将影像与就诊、病历、收费关联,用于检查进度跟进、复诊查阅、经营分析,以及追溯当时使用的分析结果。如果不留存,这些业务就需要反复查询美亚并临时关联数据,受其接口可用性和查询能力限制;分析结果更新或撤销后,若美亚不提供历史版本,SaaS 就难以还原当时使用的依据。
</p>
<h3>
3.2 新老影像怎么归一
</h3>
<p><strong>
业务语义归一,来源实现分开。
</strong>
新老功能可以保留,实际择一使用;对下游都应能回答同一组问题:
</p>
<ul>
<li>
影像属于谁、在哪里拍摄,是什么类型、什么时候拍摄;
</li>
<li>
当前是否有效,怎样查看,有哪些分析结果。
</li>
</ul>
<p><strong>
如果下游直接依赖“老影像的一套字段”和“美亚的另一套字段”,每切换一次影像系统,下游都要跟着改。
</strong>
统一表达后,来源切换主要由 SaaS 处理,PAC 继续按宿主事实消费。
</p>
<p>
但统一不等于强行写入旧表。美亚有检查、切片、绑定状态和外部文件标识;如果旧结构表达不了,不能为了兼容丢掉它们,也不能把临时链接塞进旧文件地址就算完成。是否复用旧表,是方案采纳后的存储设计选择。
</p>
<p
class=
"footnote-callout"
>
如果未来把历史影像迁入美亚,应保留新旧标识的对应关系。
<strong>
同一张片换了存储位置,不应被当成一次新的拍摄或重复的分析依据。
</strong></p>
</div></section>
<section
class=
"chapter"
id=
"pac"
aria-labelledby=
"heading-pac"
><header
class=
"chapter-head"
><span
class=
"chapter-num"
aria-hidden=
"true"
>
04
</span><div><p
class=
"chapter-kicker"
>
PAC
</p><h2
id=
"heading-pac"
>
PAC:用分析形成诊断信号,通过宿主查看影像
</h2></div></header><div
class=
"prose"
><p>
PAC 在这条链路中的职责保持两项:
</p>
<ol
class=
"responsibilities"
>
<li><span
class=
"responsibility-icon"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m12 3 10 5-10 5L2 8l10-5ZM2 12l10 5 10-5M2 16l10 5 10-5"
></path></svg></span><strong>
消费影像分析。
</strong>
将宿主提供的有效分析结果作为诊断信号源之一,与其他患者事实共同用于画像与召回,并保留来源依据。
</li>
<li><span
class=
"responsibility-icon"
><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m10 13 4-4m-6 6-2 2a4 4 0 0 1-6-6l5-5a4 4 0 0 1 6 0m2 3 2-2a4 4 0 0 1 6 6l-5 5a4 4 0 0 1-6 0"
transform=
"translate(1 1) scale(.9)"
></path></svg></span><strong>
提供影像查看入口。
</strong>
沿用宿主槽位打开 FridaySaaS,由宿主处理新老影像入口、权限和阅片授权。
</li>
</ol>
<p>
美亚生成的分析,由 SaaS 接收、留底后推给 PAC。
<strong>
如果 PAC 每次处理事实都临时向美亚取结果,重放同一份数据时可能拿到不同内容,就无法解释当时为什么得出那个判断。
</strong></p>
<p>
PAC 可以知道数据来源是美亚,但美亚不应成为 PAC 核心的直接依赖。
<strong>
否则接入下一家影像供应商时,PAC 又要接一套鉴权、患者映射和同步机制,还可能与 SaaS 的权限及归属规则形成两套判断。
</strong>
这些差异应由宿主消化。
</p>
<details
class=
"future"
><summary><span><span
class=
"note-label"
>
未来扩展
</span>
如果由 PAC 主动发起分析
</span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m9 5 7 7-7 7"
></path></svg></summary><div
class=
"detail-body"
><p>
如果将来是 PAC 自己发起分析,那是另一项能力:
</p><ul>
<li>
PAC 通过宿主获得授权的文件;
</li>
<li>
PAC 调用自己选择的分析服务;
</li>
<li>
结果标明由 PAC 发起,保留实际分析服务来源及结果版本,并关联原始影像。
</li>
</ul></div></details>
</div></section>
<section
class=
"chapter"
id=
"review"
aria-labelledby=
"heading-review"
><header
class=
"chapter-head"
><span
class=
"chapter-num"
aria-hidden=
"true"
>
05
</span><div><p
class=
"chapter-kicker"
>
REVIEW
</p><h2
id=
"heading-review"
>
评审要确定什么,哪些仍需外部确认
</h2></div></header><div
class=
"prose"
><p>
本轮评审确认的是职责和数据留存方向:
</p>
<div
class=
"table-wrap"
><table>
<thead>
<tr>
<th
scope=
"col"
>
系统
</th>
<th
scope=
"col"
>
对下游承担的承诺
</th>
</tr>
</thead>
<tbody><tr>
<td
data-label=
"系统"
>
美亚
</td>
<td
data-label=
"对下游承担的承诺"
>
影像与分析可查询,变更可获取,原片可授权访问
</td>
</tr>
<tr>
<td
data-label=
"系统"
>
FridaySaaS
</td>
<td
data-label=
"对下游承担的承诺"
>
患者归属正确,新老影像统一表达,业务数据可留底、补推和核对
</td>
</tr>
<tr>
<td
data-label=
"系统"
>
PAC
</td>
<td
data-label=
"对下游承担的承诺"
>
消费宿主分析作为诊断信号,通过宿主入口查看影像
</td>
</tr>
</tbody></table></div>
<p>
方案采纳前,优先向美亚确认两件事:
</p>
<ul
class=
"open-questions"
>
<li><span
class=
"question-no"
>
01
</span><strong>
变更怎样获取
</strong>
有无通知;没有通知时,能否完整拉到新增、修订、撤销和归属变化,并补查遗漏。
</li>
<li><span
class=
"question-no"
>
02
</span><strong>
分析怎样产生和识别
</strong>
何时触发,怎样区分未分析、处理中、失败、完成零发现,以及结果后来是否修改。
</li>
</ul>
<p>
这两项决定后续采用通知还是轮询,以及分析能否可靠成为诊断信号。具体字段、存储结构、调度周期和任务拆分,在方向采纳、能力确认后再制定。
</p>
</div></section>
<section
class=
"sources-section"
id=
"sources"
aria-labelledby=
"heading-sources"
><details
open
><summary><span><span
class=
"sources-title"
id=
"heading-sources"
>
依据与核对范围
</span><span
class=
"sources-intro"
>
本地代码、SDK 与三份美亚资料
</span></span><svg
class=
"icon "
viewBox=
"0 0 24 24"
aria-hidden=
"true"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"m9 5 7 7-7 7"
/></svg></summary><div
class=
"sources-content"
><p>
本稿依据 2026-10-16 已核对的本地代码、SDK 和以下资料;未验证美亚实际部署能力。接口细节存在版本差异,正式对接仍需双方确认。
</p>
<ul>
<li><span
title=
"本机参考文件,未随归档分发"
>
美亚系统集成方案
</span>
:第 2–3 章职责及信息同步、第 4–5 章设备与前置机、第 6–7 章阅片与患者映射。
</li>
<li><span
title=
"本机参考文件,未随归档分发"
>
美亚私有化部署方案
</span>
:明确规划阿里云 OSS 和算法资源;规划不代表当前能力已启用。
</li>
<li><span
title=
"本机参考文件,未随归档分发"
>
美亚接口文档
</span>
:§6.3 分析查询及空结果含义、§6.4/6.8 文件引用与临时 URL。
</li>
<li>
SaaS 代码:
<span
title=
"本机参考文件,未随归档分发"
>
PACS 接口
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
同步与查询
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
新版影像中心
</span>
。
</li>
<li>
PAC 代码:
<span
title=
"本机参考文件,未随归档分发"
>
影像分析事实
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
宿主影像入口
</span>
。
</li>
</ul>
<p>
资料链接指向本机原件;内部分享时可随报告提供原 PDF。
</p>
</div></details></section>
<footer
class=
"document-footer"
><span>
美亚 × FridaySaaS × PAC
<br>
影像与分析数据方案设计 · 内部评审稿
</span><a
href=
"#document"
>
回到开头 ↑
</a></footer>
</main></div><div
class=
"toast"
id=
"toast"
role=
"status"
aria-live=
"polite"
hidden
></div>
<script>
(()
=>
{
const
root
=
document
.
documentElement
,
media
=
window
.
matchMedia
(
'(prefers-color-scheme:dark)'
);
const
toast
=
document
.
getElementById
(
'toast'
);
let
toastTimer
;
function
say
(
text
){
toast
.
textContent
=
text
;
toast
.
hidden
=
false
;
clearTimeout
(
toastTimer
);
toastTimer
=
setTimeout
(()
=>
toast
.
hidden
=
true
,
1800
)}
function
theme
(){
return
root
.
dataset
.
theme
||
(
media
.
matches
?
'dark'
:
'light'
)}
function
themeLabel
(){
document
.
getElementById
(
'theme-button'
).
setAttribute
(
'aria-label'
,
theme
()
===
'dark'
?
'切换浅色模式'
:
'切换深色模式'
)}
document
.
getElementById
(
'theme-button'
).
addEventListener
(
'click'
,()
=>
{
const
next
=
theme
()
===
'dark'
?
'light'
:
'dark'
;
root
.
dataset
.
theme
=
next
;
try
{
localStorage
.
setItem
(
'imaging-report-theme'
,
next
)}
catch
{}
themeLabel
();
say
(
next
===
'dark'
?
'已切换深色阅读'
:
'已切换浅色阅读'
)});
themeLabel
();
media
.
addEventListener
?.(
'change'
,
themeLabel
);
const
font
=
document
.
getElementById
(
'font-button'
);
function
fontState
(){
const
large
=
root
.
dataset
.
size
===
'large'
;
font
.
setAttribute
(
'aria-pressed'
,
String
(
large
));
font
.
dataset
.
active
=
String
(
large
);
font
.
setAttribute
(
'aria-label'
,
large
?
'切换标准字号'
:
'切换较大字号'
)};
fontState
();
font
.
addEventListener
(
'click'
,()
=>
{
const
large
=
root
.
dataset
.
size
!==
'large'
;
root
.
dataset
.
size
=
large
?
'large'
:
'normal'
;
try
{
localStorage
.
setItem
(
'imaging-report-size'
,
root
.
dataset
.
size
)}
catch
{}
fontState
();
update
();
say
(
large
?
'已使用较大字号'
:
'已恢复标准字号'
)});
document
.
getElementById
(
'print-button'
).
addEventListener
(
'click'
,()
=>
window
.
print
());
const
printStates
=
new
Map
();
window
.
addEventListener
(
'beforeprint'
,()
=>
{
document
.
querySelectorAll
(
'details'
).
forEach
(
d
=>
{
printStates
.
set
(
d
,
d
.
open
);
d
.
open
=
true
})});
window
.
addEventListener
(
'afterprint'
,()
=>
{
printStates
.
forEach
((
open
,
d
)
=>
d
.
open
=
open
);
printStates
.
clear
()});
const
links
=
[...
document
.
querySelectorAll
(
'.toc a'
)],
sections
=
links
.
map
(
a
=>
document
.
querySelector
(
a
.
getAttribute
(
'href'
)));
let
queued
=
false
;
function
update
(){
const
max
=
document
.
documentElement
.
scrollHeight
-
window
.
innerHeight
;
const
progress
=
max
>
0
?
Math
.
min
(
1
,
Math
.
max
(
0
,
window
.
scrollY
/
max
)):
0
;
document
.
querySelector
(
'.page-progress'
).
style
.
transform
=
'scaleX('
+
progress
+
')'
;
document
.
querySelector
(
'.reading-track span'
).
style
.
width
=
Math
.
round
(
progress
*
100
)
+
'%'
;
document
.
getElementById
(
'progress-value'
).
textContent
=
Math
.
round
(
progress
*
100
)
+
'%'
;
let
index
=
0
;
sections
.
forEach
((
s
,
i
)
=>
{
if
(
s
.
getBoundingClientRect
().
top
<
190
)
index
=
i
});
if
(
progress
>
.
99
)
index
=
links
.
length
-
1
;
links
.
forEach
((
a
,
i
)
=>
{
a
.
classList
.
toggle
(
'active'
,
i
===
index
);
if
(
i
===
index
)
a
.
setAttribute
(
'aria-current'
,
'location'
);
else
a
.
removeAttribute
(
'aria-current'
)});
queued
=
false
}
const
queue
=
()
=>
{
if
(
!
queued
){
queued
=
true
;
requestAnimationFrame
(
update
)}};
window
.
addEventListener
(
'scroll'
,
queue
,{
passive
:
true
});
window
.
addEventListener
(
'resize'
,
queue
);
document
.
querySelectorAll
(
'details'
).
forEach
(
d
=>
d
.
addEventListener
(
'toggle'
,
queue
));
update
();
})();
</script>
</body>
</html>
docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review.md
0 → 100644
View file @
15d08d44
# 美亚 × FridaySaaS × PAC:影像与分析数据方案设计
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。 原方案中的影像事件推送未被采用,当前实现按诊所分页轮询并批量查询分析。
> 内部评审稿 v0.2 · 2026-10-16
> 本轮先确认设计方向与系统边界;字段、表结构、调度周期和开发任务在方案采纳后细化。
**建议边界:美亚管理影像资产,FridaySaaS 管理业务归属并负责对外同步,PAC 消费宿主提供的事实。PAC 核心不直接接美亚。**
判断依据是:
**三个系统可以保存和使用同一份患者与影像关系,但应明确各自数据的权威来源,并让关联及其变更沿统一链路传递。**
FridaySaaS 提供业务患者身份,美亚维护影像资产及其患者映射;SaaS 将两者对应关系校验、留存后提供给 PAC,PAC 据此形成事实和诊断信号。
## 1. 整体关系:数据供给与查看原片分开
**① 关联基础:先明确影像属于谁**
FridaySaaS 向美亚同步患者、诊所和就诊信息,美亚据此关联影像;SaaS 校验并留存对应关系,再向 PAC 提供。
**② 数据供给:同步影像信息与分析结果**
以下以启用美亚新影像为例,表示目标链路:
```
mermaid
flowchart TB
E[诊所设备与前置机] --> M[美亚 PACS]
M -->|存储原片| O[OSS]
M -->|影像信息、分析结果及变更| S["FridaySaaS<br/>校验归属、留存业务记录、对外同步"]
S -->|宿主事实| P["PAC<br/>诊断信号、画像与召回"]
```
原片由美亚管理,SaaS 向 PAC 提供影像业务信息与分析结果。美亚到 SaaS 如何获取变更,仍需确认。
新老影像功能可以保留,实际使用时择一启用;选用旧影像时,也由 SaaS 统一向 PAC 提供数据。
**③ 查看原片:用户按需打开宿主入口**
PAC 宿主槽位 → FridaySaaS 影像页面 → 当前选用的影像系统。
SaaS 负责权限和入口;启用新影像时,由美亚提供阅片服务。
**数据同步持续进行,查看原片由用户按需发起。**
## 2. 美亚:最重要的承诺是“变化可获取”
美亚负责采集接入、原片存储、影像关联、阅片,以及其提供的分析。对外不仅要能查询当前数据,还要让 SaaS 知道数据发生了什么变化。
**如果只能查新影像列表,SaaS 可以知道“多了一张片”,却未必知道这张片后来分析完成、被改绑到另一位患者,或被撤销。下游就可能继续使用过期甚至归属错误的依据。**
因此,对美亚真正要提的要求是:
> 提供可追溯、可补拉的影像及分析变更机制,包含新增、修改、撤销和归属变化。
| 美亚提供的能力 | FridaySaaS 如何获取 | 意味着什么 |
|---|---|---|
| 有事件通知,并能补查变更 | 通知后同步,遗漏时补拉 | 及时性较好,也能恢复遗漏 |
| 没有通知,有完整变更查询 | 定时拉取 | 可以可靠同步,延迟取决于拉取周期 |
| 只有入库时间列表、逐张查询 | 定时发现新片,反复查询分析 | 能做部分同步,但后续修订、改绑、撤销的完整性没有保障 |
## 3. FridaySaaS:留业务记录,统一新老来源
### 3.1 为什么需要留新影像数据
**如果 SaaS 只在用户打开页面时代理查询美亚,它承担的是展示功能,无法承担稳定的下游数据供给。**
没人打开页面时,PAC 仍需要知道患者有了新的影像或分析。
既然 PAC 从 SaaS 接收数据,SaaS 就应保存一份能够重试、重推、核对的影像业务记录:
| 应保存的信息 | 用途 |
|---|---|
| 患者、租户与影像采集诊所 | 明确业务归属;采集诊所不随患者后续转店而改变 |
| 影像来源、影像与检查标识 | 稳定识别、去重和追溯 |
| 类型、拍摄时间、绑定及有效状态 | 解释这份影像的业务含义 |
| 稳定文件引用,如 OSS ID | 后续获取原片;临时链接过期后仍可定位 |
| 分析结果、来源及版本 | 保留当时依据,支持补推和重放 |
| 同步进度与失败记录 | 知道哪些已交付、哪些还需要补齐 |
原片继续由美亚管理。SaaS 保存索引和结果,不重复建设影像存储;临时签名 URL 不能作为永久地址。
**这样即使美亚暂时不可用,SaaS 和 PAC 仍可查询已留存的影像信息和分析结果;原片访问和最新变更同步则可能受影响。**
分析结果会影响后续业务判断,值得留存,而不应仅在查看页面时临时取一次。
美亚接入、获取变更和归属校验由 SaaS 对接层负责;open-connector 从 SaaS 已保存的业务记录向 PAC 推送。供应商协议与下游数据传输各自负责自己的部分。
即使不接 PAC,留存业务记录也能支撑 SaaS 自身业务:将影像与就诊、病历、收费关联,用于检查进度跟进、复诊查阅、经营分析,以及追溯当时使用的分析结果。如果不留存,这些业务就需要反复查询美亚并临时关联数据,受其接口可用性和查询能力限制;分析结果更新或撤销后,若美亚不提供历史版本,SaaS 就难以还原当时使用的依据。
### 3.2 新老影像怎么归一
**业务语义归一,来源实现分开。**
新老功能可以保留,实际择一使用;对下游都应能回答同一组问题:
-
影像属于谁、在哪里拍摄,是什么类型、什么时候拍摄;
-
当前是否有效,怎样查看,有哪些分析结果。
**如果下游直接依赖“老影像的一套字段”和“美亚的另一套字段”,每切换一次影像系统,下游都要跟着改。**
统一表达后,来源切换主要由 SaaS 处理,PAC 继续按宿主事实消费。
但统一不等于强行写入旧表。美亚有检查、切片、绑定状态和外部文件标识;如果旧结构表达不了,不能为了兼容丢掉它们,也不能把临时链接塞进旧文件地址就算完成。是否复用旧表,是方案采纳后的存储设计选择。
如果未来把历史影像迁入美亚,应保留新旧标识的对应关系。
**同一张片换了存储位置,不应被当成一次新的拍摄或重复的分析依据。**
## 4. PAC:用分析形成诊断信号,通过宿主查看影像
PAC 在这条链路中的职责保持两项:
1.
**消费影像分析。**
将宿主提供的有效分析结果作为诊断信号源之一,与其他患者事实共同用于画像与召回,并保留来源依据。
2.
**提供影像查看入口。**
沿用宿主槽位打开 FridaySaaS,由宿主处理新老影像入口、权限和阅片授权。
美亚生成的分析,由 SaaS 接收、留底后推给 PAC。
**如果 PAC 每次处理事实都临时向美亚取结果,重放同一份数据时可能拿到不同内容,就无法解释当时为什么得出那个判断。**
PAC 可以知道数据来源是美亚,但美亚不应成为 PAC 核心的直接依赖。
**否则接入下一家影像供应商时,PAC 又要接一套鉴权、患者映射和同步机制,还可能与 SaaS 的权限及归属规则形成两套判断。**
这些差异应由宿主消化。
如果将来是 PAC 自己发起分析,那是另一项能力:
-
PAC 通过宿主获得授权的文件;
-
PAC 调用自己选择的分析服务;
-
结果标明由 PAC 发起,保留实际分析服务来源及结果版本,并关联原始影像。
## 5. 评审要确定什么,哪些仍需外部确认
本轮评审确认的是职责和数据留存方向:
| 系统 | 对下游承担的承诺 |
|---|---|
| 美亚 | 影像与分析可查询,变更可获取,原片可授权访问 |
| FridaySaaS | 患者归属正确,新老影像统一表达,业务数据可留底、补推和核对 |
| PAC | 消费宿主分析作为诊断信号,通过宿主入口查看影像 |
方案采纳前,优先向美亚确认两件事:
-
**变更怎样获取**
:有无通知;没有通知时,能否完整拉到新增、修订、撤销和归属变化,并补查遗漏。
-
**分析怎样产生和识别**
:何时触发,怎样区分未分析、处理中、失败、完成零发现,以及结果后来是否修改。
这两项决定后续采用通知还是轮询,以及分析能否可靠成为诊断信号。具体字段、存储结构、调度周期和任务拆分,在方向采纳、能力确认后再制定。
## 6. 兜底方案(低优先级)
若 FridaySaaS 暂不留存和同步新影像业务数据,可由
**PAC 宿主适配层直接拉取美亚现有接口**
。采用
**主动轮询、快照对账、幂等更新和采集留底**
:按入库时间发现新增,周期复查历史影像及分析,再转换为 PAC 标准事实。患者归属与授权沿用 SaaS,查看原片仍走宿主入口。
这条路线无需等待美亚推送或 SaaS 新增业务存储,但采集维护成本转至 PAC 适配层;时效受轮询周期限制,无法保证捕获所有中间变更,也无法仅凭现有空结果或查询不到准确判断分析状态及删除。
---
### 依据与核对范围
本稿依据 2026-10-16 已核对的本地代码、SDK 和以下资料;未验证美亚实际部署能力。接口细节存在版本差异,正式对接仍需双方确认。
-
美亚系统集成方案(本机参考文件
`瑞尔口腔影像中心PACS项目系统集成方案v0.4.pdf`
,未随归档分发):第 2–3 章职责及信息同步、第 4–5 章设备与前置机、第 6–7 章阅片与患者映射。
-
美亚私有化部署方案(本机参考文件
`瑞尔PACS影像中心私有化部署方案v0.1.pdf`
,未随归档分发):明确规划阿里云 OSS 和算法资源;规划不代表当前能力已启用。
-
美亚接口文档(本机参考文件
`PACS_瑞尔接口对接文档.pdf`
,未随归档分发):§6.3 分析查询及空结果含义、§6.4/6.8 文件引用与临时 URL。
-
SaaS 代码:PACS 接口(外部仓库:
`friday-saas/emr/src/main/java/com/emr/project/medical/controller/PacsController.java`
)、同步与查询(外部仓库:
`friday-saas/emr/src/main/java/com/emr/project/medical/service/impl/PacsSyncServiceImpl.java`
)、新版影像中心(外部仓库:
`friday-saas-v2/apps/web/features/patient/components/pacs-image-center.tsx`
)。
-
PAC 代码:
[
影像分析事实
](
../../../apps/pac-service/src/modules/sync/pipeline/parsers/image-analysis.parser.ts
)
、
[
宿主影像入口
](
../../../apps/pac-web/src/lib/image-action-url.ts
)
。
资料链接指向本机原件;内部分享时可随报告提供原 PDF。
docs/_archive/2026-10-operations-and-design/2026-10-18-business-date-shanghai-test-validation.md
0 → 100644
View file @
15d08d44
# 下游北京业务日期修复验收(2026-10-18)
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。
## 边界与改动
-
时刻继续以 UTC 保存;下游按
`Asia/Shanghai`
解释业务日期。
-
生日、回访任务日、
`@db.Date`
保持纯日期语义,不将数据库的 UTC 零点载体当成事件时刻。
-
修复上次到诊派生日期、同日就诊计数、同日病历/医嘱关联、未来回访排除、外院治疗回访日期比较、周岁及生日边界、AI 日期话术、患者详情与批次日期展示。
-
到诊分档按北京自然日稳定划分;原有天数阈值不变。下次回访表单按北京时间提交。
-
摄入、源时区转换、事实字段、数据库结构、召回阈值与按实际时长计算的冷却窗口均未修改。
## 本地验证
-
后端完整回归:145 套、2429 项通过;3 套、24 项按原有条件跳过。
-
前端:5 套、77 项通过;前后端 TypeScript 检查通过。
-
实际 PostgreSQL 在 UTC、北京、洛杉矶会话时区分别验证:同日/前日/次日回访、未来回访、生日年龄均一致;事务只读且回滚。
-
Node 三种进程时区验证一致;覆盖北京零点、跨月年、闰日、纯日期、摄入零点锚、到诊 DATE 派生及同日医生关联。
-
两处旧禁忌边界测试改为完整时刻断言:北京 2026-10-28 零点对应
`2026-10-27T16:00:00Z`
。
## 存量与发布范围
-
读取时计算的日期随发布生效;已持久化的上次到诊、画像特征和已生成话术不会因代码发布全部自动改写。
-
历史画像须走已有患者定向强制重算;未运行全库重算、未重摄入事实。部署测试后用真实患者定向核验。
-
本次顺序:功能分支 → test → 测试发布与验证 → 同一功能分支独立合入 main。生产不发布。
## 已完成发布与验收
-
功能分支:
`codex/business-date-shanghai`
,提交
`2e5e22e`
。
-
test 合并及实际部署:
`daff13e`
。
-
main 独立合并:
`b7901c9`
;已确认功能提交同时属于 main/test 历史,未将 test 合入 main。
-
测试发布退出码 0,57 个已有迁移无待执行项;实际容器镜像与新构建镜像一致。
-
测试健康接口返回
`status: ok`
,网页 HTTP 200;前后端容器运行且重启计数为 0。
-
在部署镜像内验证北京零点、周岁边界通过。
-
选取 Friday 测试宿主 3 位真实患者,调用实际到诊派生字段刷新逻辑;日期分别为 2026-10-16、2026-10-13、2026-09-21,与 PostgreSQL 北京日期计算一致。同日去重计数分别为 1、2、1。
-
3 个样本刷新前后事实行摘要一致,未改摄入数据;这些样本原有末诊日期本就正确,不能据此声称修复了全部历史数据。跨日纠错由本地边界用例验证。
-
仅刷新所选患者的到诊派生字段,未运行全量画像或召回重算;已生成话术未批量重建。
-
生产未部署。主目录已有未提交文件、本地服务和美亚工作区未操作。
服务器验收与回退证据保留于测试机
`/root/pac-release-backups/business-date-20261018/`
;本文未包含患者身份与凭据。
docs/_archive/2026-10-operations-and-design/2026-10-18-jvs-complaint-ingestion-prod-validation.md
0 → 100644
View file @
15d08d44
# 5i5ya 投诉摄入:生产发布与真实数据验收
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。
日期:2026-10-18(Asia/Shanghai)
**结论:已合入 main 并部署生产;34 个真实患者身份对应的 75 张反馈单、184 条跟进,落库与重复摄入核验通过。**
## 版本与发布范围
-
功能提交:
`3d29ada`
,分支
`codex/jvs-complaint-ingestion`
。
-
main 合并及生产版本:
`1793c1bcdd43ea70d420a5d00c549d1c9503fd59`
。
-
test 保持已验收版本
`c9360bcb853c3cd7d20ad1201e5e82c1e243a96b`
。
-
从功能分支合入 main,没有将 test 上的其他功能带入生产。
-
FridaySaaS 投诉未接入,召回规则未修改;本次没有新增数据库迁移。
-
沿用此前同一功能提交的本地测试结果:143 组、2,411 项通过,另行真实样本及隔离 PostgreSQL 验证 26 项通过。本轮执行生产构建及真实数据库验收。
## 发布前任务与部署
发布准备时摄入已结束,队列没有执行中的任务,但数据库仍有召回筛选长查询。不能仅凭摄入账本和队列为空判断系统空闲。用户明确允许中断本轮后,执行应用重建。
构建最初因 Dockerfile 外部解析器镜像下载失败而中止;使用删除首行解析器指令的临时 Dockerfile 副本,交由 Docker 内置解析器完成构建。其余构建指令、源码及生产构建参数保持一致,未修改仓库 Dockerfile。发布采用既有脚本的迁移、强制重建及验收流程,构建步骤替换为校验已构建镜像 ID 和固定提交。
生产约 17:21 完成应用替换:
-
57 份现有迁移,无待执行迁移。
-
service 镜像:
`1a258da2c107d5c2c2320a8075f27f5feb306791352f454ab3c1f5fbdfb830c3`
。
-
web 镜像:
`96824485987118fba9bac734b1c943134ec64e1b2018f820f5920361358f8283`
。
-
两容器实际镜像均与构建结果一致,检查时重启次数均为 0。
-
`/health`
返回
`status: ok`
;前端本机、私网及生产公网
`/plans`
均为 HTTP 200。
-
使用 managed 配置,只替换 PAC 应用容器,未重启托管 PostgreSQL/Redis。
-
旧版本
`76e7d5b`
和旧镜像保留,私有发布证据:
`/data/pac-release-backups/complaint-20261018/`
。
## 真实数仓到生产 PAC
发布后通过生产环境连接重新只读查询
`dw_group.fact_complain_out`
,使用正式
`sync-incremental`
流程摄入。患者范围限定为 34 个品牌+患者身份,单批 50、并发 1,使用
`--no-recompute`
,未另启全量画像或召回计算。
| 验收项 | 结果 |
|---|---:|
| 源投诉及跟进行 | 194 |
| 聚合反馈单 | 75 |
| 有效未完成投诉 | 4 |
| 有效已完成投诉 | 20 |
| 非有效快照 | 51 |
| 跟进记录 | 184 |
| 逐条落库一致 | 75 / 75 |
| 投诉事务 / 事实版本 | 75 / 75 |
| 归属、内容、状态、时间差异 | 0 |
51 张非有效快照包括表扬、中性、未知或草稿等,保留为
`invalidated`
,不作为有效投诉信号。
首轮于 17:22:45 完成,cohort 处理 9.392 秒,运行 ID
`5c06e2f9-89e4-4712-b923-08ddff41c848`
。正式 cohort 会同时处理这些患者的其他已有资源:整批新增 360 条事务、80 条事实,其中投诉为 75 条;整批及事实失败数均为 0。
逐条核对患者外部 ID、租户、品牌、诊所、状态、正文与标准字段、发生时间、来源版本时间和完整跟进;另以显式
`+08:00`
独立转换源时间,75 张反馈单及 184 条跟进均无差异。
第二轮于 17:23:12 完成,cohort 处理 7.333 秒,运行 ID
`1a30e0f6-f96f-45ef-8db8-61f72064e969`
。整批新增事务为 0、事实新增及替换均为 0;投诉仍为 75 条事务和 75 个事实版本,事实指纹不变,幂等验收通过。
两轮均确认全局增量游标未因抽样验收而推进。原始样本、患者清单和核对证据仅留在私有验收目录,不进入 Git。
## 后续自动摄入与边界
生产已注册
`jvs-dw`
常规调度:
`15 0,8-22/2 * * *`
,时区 Asia/Shanghai;验收结束后的下一次计划为当日 18:15。投诉历史发现由常规调度继续执行,本次 34 人验收不代表全量投诉已补齐。
最终检查无执行中的摄入、画像或召回账本记录,无活动数据库查询;画像队列另有 1 条延迟任务。未手动启动被中断召回轮次的全量重算。
源端没有 ETL 入仓时间及删除标记,窗口外迟到、时间字段完全不变的原地修订和物理删除仍属于既有采集边界。本次未增加对账机制。
主目录未提交文档、本地服务和美亚工作区未改动。本验收报告留在投诉独立工作区,暂未提交。
docs/_archive/2026-10-operations-and-design/2026-10-18-jvs-complaint-ingestion-test-validation.md
0 → 100644
View file @
15d08d44
# 5i5ya 投诉摄入:测试发布与真实数据验收
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。
日期:2026-10-18(Asia/Shanghai)
**结论:已合入 test 并部署测试环境;34 个真实患者身份、75 张反馈单的两轮摄入与逐条核验通过。main 和生产未变更。**
## 发布版本与范围
-
功能分支:
`codex/jvs-complaint-ingestion`
。
-
功能提交:
`3d29ada`
。
-
test 合并提交及服务器运行版本:
`c9360bcb853c3cd7d20ad1201e5e82c1e243a96b`
。
-
main 保持:
`76e7d5bc69e98d9f61d4a967c131c4a58d3d0207`
。
-
FridaySaaS 投诉未接入;召回规则、画像提取器、免打扰规则未修改。
-
主目录未提交文件和本地服务未改动。美亚工作区未改动。
复查重点:投诉按配置命名空间聚合,沿用宿主患者身份;变化发现与完整跟进读取分开;新资源首轮发现历史患者;北京时间统一转 UTC;非负面、草稿和未知数据保存无效快照;重复和旧快照重放不恢复已撤销的投诉效力。
## 本次本地验证
-
回归测试:143 组、2,411 项通过;24 项按配置跳过。
-
另行显式启用真实源样本和独立 PostgreSQL 验证:2 组、26 项通过;SQL 导出辅助项未启用。
-
`@pac/types`
构建通过。
-
后端源码与测试的类型检查通过。
-
提交前差异检查通过。
真实样本及数据库专项验证覆盖同时间分类修订、旧版本重放、跨品牌同号、身份冲突、完整跟进和 UTC 转换。测试库与本地应用数据库隔离。
## 测试发布检查
发布前测试服务器在 test 分支,工作目录干净;健康接口正常,磁盘剩余约 111 GB;摄入账本无 running 任务,上轮摄入已于 16:34:36 完成。
使用现有发布脚本完成镜像构建、迁移检查和应用容器替换:
-
57 份现有迁移,无待执行迁移;本改动无新表和迁移。
-
service 镜像:
`c5932b8708e0c39d85e5b230435c267745a66f3119d6c96a69c6524f063ba8bd`
。
-
web 镜像:
`15746e42828714c27e8866ba2b6446f778452fff2bb7c0ce561c975c9b8c60ff`
。
-
容器实际镜像与构建结果一致;最终检查两容器重启次数均为 0。
-
`/health`
返回
`status: ok`
,前端 HTTP 200。
-
旧提交和镜像已留存,测试服务器发布证据位于
`/root/pac-release-backups/complaint-20261018/`
(私有目录)。
## 真实数仓 → 测试 PAC
从测试容器重新只读查询数仓;不是将本地造数写入测试库。
| 验收内容 | 实际结果 |
|---|---:|
| 患者范围(品牌+患者 ID) | 34 |
| 原始投诉及跟进行 | 194 |
| 聚合后的反馈单 | 75 |
| 有效、未完成投诉 | 4 |
| 有效、已完成投诉 | 20 |
| 非有效投诉快照 | 51 |
| 保留的跟进 | 184 |
| 逐条事实核验一致 | 75 / 75 |
| 投诉事务 | 75 |
| 投诉事实版本 | 75 |
| 核验差异 | 0 |
非有效快照包括表扬、中性、未知或草稿等,均为
`invalidated`
、
`signal_eligible=false`
,不计入有效投诉。有效投诉合计 24 张。
通过正式
`sync-incremental`
CLI,使用限定的患者清单、单批 50 人、并发 1、
`--no-recompute`
运行两轮。沿用完整宿主 cohort 管线,因此同批患者其他资源也按已有机制正常摄入;没有另开全量画像或召回计算。
首轮:16:47:20 完成,cohort 处理约 9.5 秒;整批失败数和事实失败数均为 0。首轮运行 ID:
`d4414cca-4495-42df-9dfc-51d47777471d`
。
核对包括:患者外部 ID、租户、品牌、投诉诊所、状态、正文及标准字段、发生时间、版本时间和完整跟进内容。另用显式
`+08:00`
独立转换源时间,75 张反馈单与 184 条跟进全部通过,未依赖服务器默认时区。
第二轮:投诉事务仍为 75,事实版本仍为 75,全部事实的内容指纹与第一轮一致,无重复新增或版本抖动。
两轮均保持原全局增量游标不变,投诉首轮全量发现水位仍未被抽样验收提前推进。其余患者的存量发现由后续常规增量调度处理;本次验收不代表全量投诉已经补齐。
## 已知边界
源端没有 ETL 入仓时间或删除标记。现有回看机制仍无法保证发现窗口外的迟到回填、全部时间字段不变的原地修订或物理删除。本次未扩展为对账任务,也未把投诉信号加入召回决策。
原始数据、患者清单及逐条比对证据保存在私有验收目录,不进入 Git;本报告仅记录汇总和运行证据。
docs/_archive/2026-10-operations-and-design/2026-10-19-meyer-friday-pac-imaging-review-document.html
0 → 100644
View file @
15d08d44
<!doctype html>
<html
lang=
"zh-CN"
><head><meta
charset=
"utf-8"
><meta
name=
"viewport"
content=
"width=device-width,initial-scale=1"
><meta
name=
"color-scheme"
content=
"light dark"
><meta
name=
"description"
content=
"美亚、FridaySaaS 与 PAC 的影像及患者分析方案,评审修订稿 v0.4。"
><title>
影像与分析数据方案设计 · v0.4
</title><style>
:root
{
color-scheme
:
light
;
--ground
:
#f4f6fb
;
--surface
:
#fff
;
--sunken
:
#eaeef6
;
--ink
:
#0f1b33
;
--ink-2
:
#44506a
;
--ink-3
:
#6f7a91
;
--brand
:
#0032a0
;
--brand-soft
:
#e3eafa
;
--rule
:
#dde3ee
;
--note
:
#f5f7fc
;
--warn-bg
:
#fff9ed
;
--warn-rule
:
#d6ae63
;
--serif
:
"Noto Serif SC"
,
"Songti SC"
,
"STSong"
,
serif
;
--sans
:
"Noto Sans SC"
,
"PingFang SC"
,
"Microsoft YaHei"
,
system-ui
,
sans-serif
;
--mono
:
"IBM Plex Mono"
,
ui-monospace
,
"SFMono-Regular"
,
Menlo
,
monospace
}
:root
[
data-theme
=
dark
]
{
color-scheme
:
dark
;
--ground
:
#0a1020
;
--surface
:
#121a2d
;
--sunken
:
#1a2440
;
--ink
:
#e7ecf6
;
--ink-2
:
#b3bdd2
;
--ink-3
:
#8994ab
;
--brand
:
#86a7ff
;
--brand-soft
:
#1d2b52
;
--rule
:
#2a3550
;
--note
:
#172037
;
--warn-bg
:
#292518
;
--warn-rule
:
#a38a55
}
@media
(
prefers-color-scheme
:
dark
){
:root:not
([
data-theme
=
light
])
{
color-scheme
:
dark
;
--ground
:
#0a1020
;
--surface
:
#121a2d
;
--sunken
:
#1a2440
;
--ink
:
#e7ecf6
;
--ink-2
:
#b3bdd2
;
--ink-3
:
#8994ab
;
--brand
:
#86a7ff
;
--brand-soft
:
#1d2b52
;
--rule
:
#2a3550
;
--note
:
#172037
;
--warn-bg
:
#292518
;
--warn-rule
:
#a38a55
}}
*
{
box-sizing
:
border-box
}
html
{
scroll-behavior
:
smooth
}
body
{
margin
:
0
;
background
:
var
(
--ground
);
color
:
var
(
--ink
);
font-family
:
var
(
--sans
);
font-size
:
16px
;
line-height
:
1.9
;
-webkit-font-smoothing
:
antialiased
}
::selection
{
background
:
var
(
--brand-soft
);
color
:
var
(
--ink
)}
a
{
color
:
var
(
--brand
);
text-decoration-color
:
color-mix
(
in
srgb
,
var
(
--brand
)
35%
,
transparent
);
text-underline-offset
:
4px
}
a
:hover
{
text-decoration-color
:
var
(
--brand
)}
button
{
font
:
inherit
}
a
:focus-visible
,
button
:focus-visible
{
outline
:
2px
solid
var
(
--brand
);
outline-offset
:
4px
}
.wrap
{
max-width
:
1040px
;
margin
:
auto
;
padding
:
40px
26px
48px
}
.document-header
{
padding
:
0
8px
28px
}
.topline
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
16px
;
align-items
:
center
;
margin-bottom
:
16px
}
.eyebrow
{
font-size
:
12px
;
letter-spacing
:
.10em
;
color
:
var
(
--brand
);
font-weight
:
700
}
.tools
{
display
:
flex
;
gap
:
4px
}
.tools
button
{
border
:
0
;
color
:
var
(
--ink-3
);
background
:
transparent
;
padding
:
5px
8px
;
cursor
:
pointer
;
font-size
:
12px
;
border-radius
:
5px
;
display
:
flex
;
align-items
:
center
;
gap
:
5px
}
.tools
button
:hover
{
background
:
var
(
--brand-soft
);
color
:
var
(
--brand
)}
.tools
svg
{
width
:
15px
;
height
:
15px
}
h1
{
font-family
:
var
(
--serif
);
font-weight
:
700
;
font-size
:
clamp
(
30px
,
3.8vw
,
42px
);
line-height
:
1.35
;
letter-spacing
:
.015em
;
margin
:
0
0
14px
}
.meta
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
gap
:
9px
;
color
:
var
(
--ink-3
);
font-size
:
12px
;
margin-bottom
:
14px
}
.meta
.version
{
background
:
var
(
--brand-soft
);
color
:
var
(
--brand
);
border-radius
:
99px
;
padding
:
1px
10px
;
font-weight
:
500
}
.meta
time
{
font-family
:
var
(
--mono
);
font-size
:
12px
}
.scope
{
max-width
:
48em
;
color
:
var
(
--ink-2
);
font-size
:
14px
;
line-height
:
1.8
;
margin
:
0
}
main
{
background
:
var
(
--surface
);
border-radius
:
14px
;
padding
:
42px
58px
40px
}
p
{
margin
:
0
0
18px
;
color
:
var
(
--ink-2
)}
strong
{
color
:
var
(
--ink
);
font-weight
:
600
}
p
:last-child
{
margin-bottom
:
0
}
.boundary
{
padding
:
0
0
0
20px
;
border-left
:
3px
solid
var
(
--brand
);
font-size
:
18px
;
line-height
:
1.85
;
margin-bottom
:
24px
}
.boundary
strong
{
font-weight
:
600
}
.principle
{
font-size
:
15.5px
;
line-height
:
1.9
;
margin-bottom
:
30px
}
.contents
{
border-top
:
1px
solid
var
(
--rule
);
border-bottom
:
1px
solid
var
(
--rule
);
padding
:
17px
0
19px
;
margin
:
32px
0
0
}
.contents-label
{
color
:
var
(
--ink-3
);
font-size
:
11px
;
letter-spacing
:
.14em
;
display
:
block
;
margin-bottom
:
9px
}
.contents-links
{
display
:
grid
;
grid-template-columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
gap
:
7px
18px
}
.contents
a
{
text-decoration
:
none
;
color
:
var
(
--ink-2
);
font-size
:
13px
;
white-space
:
nowrap
}
.contents
a
:hover
{
color
:
var
(
--brand
)}
.contents
a
span
{
font-family
:
var
(
--mono
);
display
:
inline-block
;
width
:
29px
;
color
:
var
(
--brand
);
font-size
:
11px
}
.chapter
{
padding-top
:
46px
;
scroll-margin-top
:
28px
}
.chapter
+
.chapter
{
margin-top
:
42px
;
border-top
:
1px
solid
var
(
--rule
)}
.chapter-head
{
display
:
flex
;
align-items
:
baseline
;
gap
:
15px
;
margin-bottom
:
24px
}
.chapter-num
{
color
:
var
(
--brand
);
font-family
:
var
(
--mono
);
font-size
:
15px
;
line-height
:
1.3
;
flex
:
none
}
.chapter
h2
{
font-family
:
var
(
--serif
);
font-size
:
26px
;
line-height
:
1.45
;
font-weight
:
700
;
margin
:
0
;
text-wrap
:
pretty
}
.chapter
h3
{
font-size
:
18px
;
line-height
:
1.6
;
color
:
var
(
--ink
);
margin
:
32px
0
14px
;
font-weight
:
600
}
.chapter-head
+
h3
{
margin-top
:
0
}
.step-heading
{
display
:
flex
;
gap
:
8px
}
.step-heading
span
{
color
:
var
(
--brand
);
font-weight
:
500
}
.figure-intro
{
font-size
:
14px
;
margin-bottom
:
12px
}
ul
,
ol
{
padding-left
:
23px
;
color
:
var
(
--ink-2
);
margin
:
14px
0
22px
}
li
{
padding-left
:
3px
;
margin
:
7px
0
}
li
::marker
{
color
:
var
(
--brand
)}
blockquote
{
margin
:
24px
0
;
padding
:
20px
24px
;
background
:
var
(
--brand-soft
);
border-left
:
3px
solid
var
(
--brand
)}
blockquote
p
{
color
:
var
(
--ink
);
font-family
:
var
(
--serif
);
font-size
:
20px
;
line-height
:
1.75
;
font-weight
:
600
}
.consequence
{
border-left
:
3px
solid
var
(
--warn-rule
);
background
:
var
(
--warn-bg
);
padding
:
17px
21px
;
margin
:
22px
0
}
.consequence
strong
{
font-weight
:
500
}
.own-business
{
border-top
:
1px
dashed
var
(
--rule
);
padding-top
:
20px
;
margin-top
:
24px
}
.future
{
margin
:
28px
0
0
;
padding
:
18px
23px
;
background
:
var
(
--note
);
font-size
:
14px
;
border-left
:
2px
solid
var
(
--rule
)}
.future
p
{
margin-bottom
:
8px
}
.future
ul
{
margin
:
7px
0
0
}
.future
li
{
margin
:
4px
0
}
.duties
{
list-style
:
none
;
padding
:
0
;
counter-reset
:
duty
;
margin
:
20px
0
24px
}
.duties
li
{
counter-increment
:
duty
;
position
:
relative
;
padding
:
15px
0
15px
42px
;
margin
:
0
;
border-top
:
1px
solid
var
(
--rule
)}
.duties
li
:last-child
{
border-bottom
:
1px
solid
var
(
--rule
)}
.duties
li
::before
{
content
:
'0'
counter
(
duty
);
position
:
absolute
;
left
:
0
;
color
:
var
(
--brand
);
font
:
14px
var
(
--mono
);
top
:
21px
}
.questions
{
padding-left
:
23px
}
.questions
li
{
margin
:
12px
0
}
.questions
strong
{
color
:
var
(
--brand
)}
.table-wrap
{
margin
:
22px
0
26px
}
table
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
14px
;
line-height
:
1.8
;
text-align
:
left
}
th
{
font-weight
:
600
;
color
:
var
(
--ink
);
background
:
var
(
--sunken
);
padding
:
12px
16px
;
border-bottom
:
1px
solid
var
(
--rule
);
vertical-align
:
top
}
td
{
padding
:
13px
16px
;
border-bottom
:
1px
solid
var
(
--rule
);
vertical-align
:
top
;
color
:
var
(
--ink-2
)}
tbody
td
:first-child
{
font-weight
:
500
;
color
:
var
(
--ink
)}
.two-col
td
:first-child
{
width
:
37%
}
.three-col
th
:nth-child
(
1
)
{
width
:
33%
}
.three-col
th
:nth-child
(
2
)
{
width
:
28%
}
.three-col
th
:nth-child
(
3
)
{
width
:
39%
}
#review
td
:first-child
{
width
:
22%
;
color
:
var
(
--brand
);
font-weight
:
600
}
figure
{
margin
:
0
}
.flow
{
padding
:
20px
24px
22px
;
border
:
1px
solid
var
(
--rule
);
border-radius
:
9px
;
margin
:
14px
0
22px
;
background
:
var
(
--note
)}
figcaption
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--ink
);
display
:
flex
;
gap
:
10px
;
align-items
:
center
;
flex-wrap
:
wrap
;
padding-bottom
:
18px
}
figcaption
>
span
{
font-size
:
11px
;
font-family
:
var
(
--mono
);
color
:
var
(
--brand
);
font-weight
:
400
}
figcaption
>
small
{
font-size
:
11px
;
font-weight
:
400
;
color
:
var
(
--ink-3
);
margin-left
:
auto
}
.flow-grid
{
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1
fr
)
104px
minmax
(
0
,
1.1
fr
)
65px
minmax
(
0
,
1
fr
);
grid-template-rows
:
auto
auto
auto
;
align-items
:
center
}
.capture
{
grid-column
:
1
;
grid-row
:
1
;
text-align
:
center
;
font-size
:
11px
;
color
:
var
(
--ink-3
);
line-height
:
1.7
;
align-self
:
end
;
padding
:
0
0
7px
}
.capture
>
span
{
display
:
block
;
color
:
var
(
--ink-3
);
font-size
:
20px
;
line-height
:
1.3
}
.system
{
grid-row
:
2
;
background
:
var
(
--surface
);
border
:
1px
solid
var
(
--rule
);
border-top
:
2px
solid
var
(
--brand
);
padding
:
16px
8px
;
text-align
:
center
;
align-self
:
stretch
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
min-width
:
0
}
.meyer
{
grid-column
:
1
}
.saas
{
grid-column
:
3
;
background
:
var
(
--brand-soft
)}
.pac
{
grid-column
:
5
}
.system-label
{
color
:
var
(
--ink-3
);
font-size
:
10px
;
letter-spacing
:
.10em
;
line-height
:
1.5
;
margin-bottom
:
6px
}
.system
>
strong
{
font-size
:
18px
;
line-height
:
1.5
;
font-weight
:
600
;
white-space
:
nowrap
}
.system
>
small
{
font-size
:
11px
;
color
:
var
(
--ink-2
);
margin-top
:
6px
;
line-height
:
1.7
}
.connector
{
grid-row
:
2
;
text-align
:
center
;
color
:
var
(
--brand
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
8px
;
padding
:
0
5px
}
.connector.first
{
grid-column
:
2
}
.connector.second
{
grid-column
:
4
}
.connector
>
span
{
font-size
:
11px
;
line-height
:
1.6
}
.connector
svg
{
width
:
38px
;
height
:
12px
;
flex
:
none
}
.connector
small
{
font-size
:
10px
;
color
:
var
(
--ink-3
);
line-height
:
1.5
}
.storage
{
grid-column
:
1
;
grid-row
:
3
;
text-align
:
center
;
color
:
var
(
--ink-3
);
font-size
:
13px
}
.storage
>
span
{
display
:
block
;
line-height
:
1.4
;
font-size
:
20px
}
.storage
>
div
{
border
:
1px
dashed
var
(
--rule
);
background
:
var
(
--surface
);
padding
:
4px
10px
}
.storage
small
{
font-size
:
11px
;
margin-left
:
5px
}
.mobile-only
{
display
:
none
}
.view-path
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
19px
20px
;
border-top
:
1px
solid
var
(
--rule
);
border-bottom
:
1px
solid
var
(
--rule
);
margin
:
18px
0
;
color
:
var
(
--ink
);
font-size
:
14px
;
font-weight
:
500
}
.view-path
b
{
color
:
var
(
--brand
);
font-size
:
18px
;
font-weight
:
400
;
line-height
:
1
}
.sources
{
margin-top
:
44px
;
padding-top
:
25px
;
border-top
:
1px
solid
var
(
--rule
);
scroll-margin-top
:
28px
;
font-size
:
12.5px
;
color
:
var
(
--ink-3
);
line-height
:
1.85
}
.sources
h2
{
font-size
:
14px
;
font-weight
:
600
;
margin
:
0
0
12px
}
.sources
p
{
color
:
var
(
--ink-3
);
margin-bottom
:
13px
}
.sources
ul
{
padding-left
:
19px
;
margin
:
14px
0
17px
}
.sources
li
{
padding-left
:
1px
;
margin
:
8px
0
;
color
:
var
(
--ink-3
)}
.sources
a
{
font-weight
:
500
}
.page-footer
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
color
:
var
(
--ink-3
);
font-size
:
11px
;
padding
:
19px
8px
0
}
.page-footer
a
{
text-decoration
:
none
;
color
:
var
(
--ink-3
)}
.back-top
{
position
:
fixed
;
right
:
24px
;
bottom
:
22px
;
z-index
:
10
;
border
:
1px
solid
var
(
--rule
);
border-radius
:
99px
;
padding
:
8px
14px
;
background
:
var
(
--surface
);
color
:
var
(
--brand
);
font-size
:
12px
;
text-decoration
:
none
;
box-shadow
:
0
2px
16px
#00000006
;
opacity
:
0
;
pointer-events
:
none
;
transition
:
opacity
.2s
}
.back-top.visible
{
opacity
:
1
;
pointer-events
:
auto
}
.skip-link
{
position
:
absolute
;
top
:
-80px
;
left
:
20px
;
background
:
var
(
--surface
);
padding
:
10px
;
z-index
:
20
}
.skip-link
:focus
{
top
:
10px
}
@media
(
min-width
:
1440px
){
.wrap
{
padding-top
:
48px
}}
@media
(
max-width
:
900px
){
.wrap
{
padding
:
26px
22px
32px
}
main
{
padding
:
34px
35px
}
.chapter
h2
{
font-size
:
24px
}
.flow
{
padding
:
18px
16px
}
.flow-grid
{
grid-template-columns
:
minmax
(
0
,
1
fr
)
90px
minmax
(
0
,
1.1
fr
)
56px
minmax
(
0
,
1
fr
)}
.system
>
strong
{
font-size
:
16px
}
.system
>
small
{
font-size
:
9px
}
.system-label
{
font-size
:
9px
}
.connector
>
span
{
font-size
:
9px
}
.connector
small
{
font-size
:
8px
}}
@media
(
max-width
:
660px
){
body
{
font-size
:
15px
;
line-height
:
1.85
}
.wrap
{
padding
:
22px
15px
28px
}
.document-header
{
padding
:
0
4px
23px
}
.topline
{
align-items
:
flex-start
;
gap
:
8px
;
margin-bottom
:
13px
}
.eyebrow
{
font-size
:
10px
;
letter-spacing
:
.04em
;
padding-top
:
7px
}
.tools
{
gap
:
0
;
flex
:
none
}
.tools
button
{
padding
:
7px
}
.tools
button
span
{
display
:
none
}
.tools
svg
{
width
:
16px
;
height
:
16px
}
h1
span
{
display
:
block
}
h1
{
font-size
:
31px
;
line-height
:
1.4
;
max-width
:
11em
;
text-wrap
:
balance
;
margin-bottom
:
13px
}
.meta
{
font-size
:
11px
;
margin-bottom
:
12px
}
.meta
time
{
font-size
:
11px
}
.scope
{
font-size
:
13px
;
line-height
:
1.8
}
main
{
padding
:
27px
22px
29px
;
border-radius
:
10px
}
.boundary
{
font-size
:
17px
;
line-height
:
1.85
;
padding-left
:
15px
;
margin-bottom
:
21px
}
.principle
{
font-size
:
14px
}
.contents
{
margin-top
:
25px
;
padding
:
15px
0
17px
}
.contents-links
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
gap
:
7px
12px
}
.contents
a
{
font-size
:
12px
}
.contents
a
span
{
width
:
25px
}
.chapter
{
padding-top
:
32px
;
scroll-margin-top
:
18px
}
.chapter
+
.chapter
{
margin-top
:
30px
}
.chapter-head
{
gap
:
10px
;
margin-bottom
:
21px
}
.chapter-num
{
font-size
:
12px
}
.chapter
h2
{
font-size
:
22px
;
line-height
:
1.5
}
.chapter
h3
{
font-size
:
16px
;
margin-top
:
28px
}
.chapter-head
+
h3
{
margin-top
:
0
}
.consequence
{
padding
:
14px
16px
}
.chapter
blockquote
{
padding
:
16px
18px
;
margin
:
22px
0
}
.chapter
blockquote
p
{
font-size
:
18px
;
line-height
:
1.8
}
.table-wrap
{
margin
:
22px
0
}
.table-wrap
thead
{
display
:
none
}
.table-wrap
table
,
.table-wrap
tbody
{
display
:
block
}
.table-wrap
tr
{
display
:
block
;
padding
:
13px
0
;
border-bottom
:
1px
solid
var
(
--rule
)}
.table-wrap
tr
:first-child
{
border-top
:
2px
solid
var
(
--brand
)}
.table-wrap
td
{
display
:
block
;
padding
:
3px
0
;
border
:
0
;
width
:
auto
!important
;
line-height
:
1.85
;
font-size
:
13px
}
.table-wrap
td
:first-child
{
font-size
:
14px
;
font-weight
:
600
;
padding
:
0
0
6px
;
color
:
var
(
--ink
)}
.three-col
td
:not
(
:first-child
)
{
display
:
grid
;
grid-template-columns
:
6.4em
1
fr
;
gap
:
10px
}
.three-col
td
:not
(
:first-child
)
::before
{
content
:
attr
(
data-label
);
font-size
:
11px
;
color
:
var
(
--ink-3
);
padding-top
:
2px
}
#review
td
:first-child
{
color
:
var
(
--brand
)}
.flow
{
padding
:
16px
;
margin
:
13px
0
20px
}
figcaption
{
font-size
:
12px
;
gap
:
8px
;
padding-bottom
:
18px
}
figcaption
>
small
{
display
:
block
;
flex-basis
:
100%
;
font-size
:
10px
;
margin
:
0
}
.flow-grid
{
display
:
flex
;
flex-direction
:
column
;
gap
:
0
}
.capture
{
font-size
:
11px
;
align-self
:
center
;
padding-bottom
:
4px
}
.system
{
align-self
:
stretch
;
padding
:
14px
;
min-height
:
95px
}
.system
>
strong
{
font-size
:
19px
}
.system
>
small
{
font-size
:
11px
}
.system-label
{
font-size
:
10px
;
margin-bottom
:
3px
}
.connector
{
padding
:
10px
0
;
gap
:
4px
}
.connector
svg
{
transform
:
rotate
(
90deg
);
width
:
28px
;
height
:
12px
;
margin
:
6px
0
}
.connector
>
span
{
font-size
:
11px
}
.connector
>
span
br
{
display
:
none
}
.connector
small
{
font-size
:
10px
}
.storage
{
margin-top
:
16px
;
align-self
:
stretch
;
font-size
:
12px
}
.storage
>
span
{
display
:
none
}
.storage
>
div
{
padding
:
7px
5px
;
background
:
transparent
}
.storage
small
{
font-size
:
11px
}
.mobile-only
{
display
:
inline
;
font-weight
:
400
}
.view-path
{
flex-direction
:
column
;
gap
:
7px
;
padding
:
16px
;
font-size
:
13px
;
align-items
:
flex-start
}
.view-path
b
{
transform
:
rotate
(
90deg
);
margin-left
:
7px
;
font-size
:
17px
;
line-height
:
1.1
}
.duties
li
{
padding-left
:
33px
}
.duties
li
::before
{
font-size
:
12px
;
top
:
20px
}
.future
{
font-size
:
13px
;
padding
:
15px
17px
}
.sources
{
margin-top
:
32px
;
font-size
:
11.5px
}
.sources
h2
{
font-size
:
13px
}
.page-footer
{
font-size
:
10px
;
flex-direction
:
column
;
gap
:
3px
;
padding
:
15px
4px
0
}
.back-top
{
right
:
12px
;
bottom
:
12px
;
font-size
:
11px
;
padding
:
6px
11px
}}
@media
(
max-width
:
350px
){
.wrap
{
padding-inline
:
10px
}
main
{
padding-inline
:
17px
}
h1
{
font-size
:
29px
}
.eyebrow
{
font-size
:
9px
}
.boundary
{
font-size
:
16px
}
.chapter
h2
{
font-size
:
20px
}
.flow
{
padding-inline
:
12px
}}
@media
(
prefers-reduced-motion
:
reduce
){
html
{
scroll-behavior
:
auto
}
*
{
transition
:
none
!important
}}
@page
{
size
:
A4
;
margin
:
16mm
17mm
17mm
}
@media
print
{
html
{
scroll-behavior
:
auto
}
:root
,
:root
[
data-theme
=
dark
]
{
color-scheme
:
light
;
--ground
:
#fff
;
--surface
:
#fff
;
--sunken
:
#edf0f6
;
--ink
:
#0f1b33
;
--ink-2
:
#33415a
;
--ink-3
:
#69758a
;
--brand
:
#0032a0
;
--brand-soft
:
#edf1fa
;
--rule
:
#d5dce9
;
--note
:
#f8f9fc
;
--warn-bg
:
#fffaf0
;
--warn-rule
:
#c1a26b
}
body
{
background
:
white
;
font-size
:
10pt
;
line-height
:
1.75
;
-webkit-print-color-adjust
:
exact
;
print-color-adjust
:
exact
}
.wrap
{
padding
:
0
;
max-width
:
none
}
.document-header
{
padding
:
0
0
17pt
}
.topline
{
margin-bottom
:
9pt
}
.eyebrow
{
font-size
:
8pt
}
h1
{
font-size
:
25pt
;
margin-bottom
:
8pt
}
.meta
{
margin-bottom
:
7pt
;
font-size
:
8pt
}
.meta
time
{
font-size
:
8pt
}
.scope
{
font-size
:
9pt
}
.tools
,
.back-top
,
.skip-link
{
display
:
none
!important
}
main
{
padding
:
0
;
border-radius
:
0
}
.boundary
{
font-size
:
11.5pt
;
margin-bottom
:
13pt
;
padding-left
:
12pt
}
.principle
{
font-size
:
9.5pt
;
margin-bottom
:
17pt
}
.contents
{
margin-top
:
17pt
;
padding
:
10pt
0
}
.contents-links
{
grid-template-columns
:
repeat
(
3
,
1
fr
);
gap
:
4pt
10pt
}
.contents
a
{
font-size
:
8pt
}
.contents-label
{
font-size
:
7pt
;
margin-bottom
:
5pt
}
.chapter
{
padding-top
:
24pt
}
.chapter
+
.chapter
{
margin-top
:
23pt
}
.chapter-head
{
gap
:
10pt
;
margin-bottom
:
13pt
;
break-after
:
avoid
}
.chapter
h2
{
font-size
:
17pt
;
line-height
:
1.45
}
.chapter-num
{
font-size
:
10pt
}
.chapter
h3
{
font-size
:
11.5pt
;
margin
:
19pt
0
9pt
;
break-after
:
avoid
}
.chapter-head
+
h3
{
margin-top
:
0
}
p
{
margin-bottom
:
10pt
;
orphans
:
3
;
widows
:
3
}
li
{
margin
:
4pt
0
}
ul
,
ol
{
margin
:
9pt
0
13pt
}
blockquote
{
margin
:
13pt
0
;
padding
:
12pt
16pt
;
break-inside
:
avoid
}
blockquote
p
{
font-size
:
12pt
}
.consequence
{
padding
:
10pt
14pt
;
margin
:
12pt
0
;
break-inside
:
avoid
}
.figure-intro
{
font-size
:
9pt
;
break-after
:
avoid
;
margin-bottom
:
7pt
}
.flow
{
margin
:
8pt
0
13pt
;
padding
:
12pt
14pt
;
break-inside
:
avoid
}
.flow-grid
{
grid-template-columns
:
minmax
(
0
,
1
fr
)
85px
minmax
(
0
,
1.1
fr
)
55px
minmax
(
0
,
1
fr
)}
figcaption
{
font-size
:
8pt
;
padding-bottom
:
12pt
}
figcaption
>
span
{
font-size
:
7pt
}
figcaption
>
small
{
font-size
:
7pt
}
.capture
{
font-size
:
7pt
}
.system
{
padding
:
10pt
5pt
}
.system-label
{
font-size
:
6.5pt
}
.system
>
strong
{
font-size
:
12pt
}
.system
>
small
{
font-size
:
6.5pt
}
.connector
>
span
{
font-size
:
6.5pt
}
.connector
small
{
font-size
:
5.5pt
}
.storage
{
font-size
:
8pt
}
.storage
small
{
font-size
:
7pt
}
.view-path
{
font-size
:
8.5pt
;
padding
:
12pt
10pt
;
break-inside
:
avoid
;
margin
:
11pt
0
}
.table-wrap
{
margin
:
13pt
0
16pt
;
break-inside
:
avoid
}
table
{
font-size
:
8.5pt
;
line-height
:
1.65
}
th
{
padding
:
8pt
10pt
}
td
{
padding
:
8pt
10pt
}
tr
{
break-inside
:
avoid
}
.duties
{
break-inside
:
avoid
}
.duties
li
{
padding
:
9pt
0
9pt
29pt
}
.duties
li
::before
{
font-size
:
9pt
;
top
:
13pt
}
.future
{
font-size
:
8.5pt
;
break-inside
:
avoid
;
padding
:
12pt
15pt
;
margin-top
:
17pt
}
.own-business
{
padding-top
:
12pt
;
margin-top
:
15pt
}
.sources
{
font-size
:
8pt
;
line-height
:
1.75
;
margin-top
:
25pt
;
padding-top
:
17pt
}
.sources
h2
{
font-size
:
9.5pt
;
break-after
:
avoid
}
.sources
li
{
margin
:
6pt
0
}
.sources
p
{
margin-bottom
:
9pt
}
.page-footer
{
font-size
:
7pt
;
padding-top
:
15pt
}
a
{
color
:
inherit
;
text-decoration
:
none
}
.sources
a
{
text-decoration
:
underline
;
text-decoration-color
:
#b9c3d8
}
.questions
{
break-inside
:
avoid
;
break-after
:
avoid
}
.review-conclusion
{
break-before
:
avoid
}
.table-wrap
:has
(+
p
)
{
break-after
:
auto
}
.keep-next
{
break-after
:
avoid
}}
.diagram-desktop
{
display
:
block
;
width
:
100%
;
height
:
auto
;
color
:
var
(
--ink
);
font-family
:
var
(
--sans
)}
.diagram-desktop
.node
{
fill
:
var
(
--surface
);
stroke
:
var
(
--rule
);
stroke-width
:
1.3
}
.diagram-desktop
.hub
{
fill
:
var
(
--brand-soft
);
stroke
:
var
(
--brand
)}
.diagram-desktop
.edge
{
stroke
:
var
(
--brand
);
stroke-width
:
1.5
;
fill
:
none
;
marker-end
:
url(#arrow)
}
.diagram-desktop
.title
{
fill
:
var
(
--ink
);
font-size
:
19px
;
font-weight
:
600
}
.diagram-desktop
.sub
{
fill
:
var
(
--ink-2
);
font-size
:
12px
}
.diagram-desktop
.label
{
fill
:
var
(
--ink-2
);
font-size
:
13px
}
.diagram-mobile
{
display
:
none
}
.contents
a
{
white-space
:
normal
}
.sources
a
{
overflow-wrap
:
anywhere
}
@media
screen
and
(
max-width
:
660px
){
.diagram-desktop
{
display
:
none
}
.diagram-mobile
{
display
:
block
}
.route
{
padding
:
13px
0
;
border-top
:
1px
solid
var
(
--rule
);
font-size
:
13px
;
line-height
:
1.85
}
.route
:first-child
{
padding-top
:
0
;
border-top
:
0
}
.route
:last-child
{
padding-bottom
:
0
}
.route
strong
{
display
:
block
;
color
:
var
(
--brand
);
font-size
:
14px
;
margin-bottom
:
3px
}
.route
span
{
color
:
var
(
--ink-2
)}
.table-wrap
td
+
td
:before
{
content
:
attr
(
data-label
)
":"
;
color
:
var
(
--ink-3
);
font-size
:
11px
}
.chapter
h2
{
font-size
:
21px
}}
@media
print
{
body
{
font-size
:
9.5pt
;
line-height
:
1.7
}
.chapter
{
padding-top
:
16pt
}
.chapter
+
.chapter
{
margin-top
:
14pt
}
.sources
{
margin-top
:
14pt
;
padding-top
:
10pt
}
.sources
ul
{
margin
:
7pt
0
}
.sources
li
{
margin
:
3pt
0
}
.sources
p
{
margin-bottom
:
6pt
}
.chapter
p
{
margin-bottom
:
8pt
}
.chapter
h3
{
margin-top
:
14pt
}
.chapter-head
{
margin-bottom
:
10pt
}
.diagram-desktop
{
display
:
block
!important
;
max-height
:
190pt
}
.diagram-mobile
{
display
:
none
!important
}
.contents
{
break-inside
:
avoid
}
.sources
{
break-inside
:
avoid
}
.chapter-head
,
h3
{
break-after
:
avoid
}
.chapter
h2
{
font-size
:
16pt
}
.flow
{
break-inside
:
avoid
}
.two-col
tr
{
break-inside
:
avoid
}}
</style></head><body>
<aside
role=
"note"
style=
"padding:12px 20px;background:#fff4da;color:#594100;border-bottom:1px solid #e9cf8d;font:14px/1.7 sans-serif"
>
历史评审稿,2026-10-22 归档。事件推送方案未被采用;现行实现按诊所分页轮询并批量查询分析。
<a
href=
"../../../apps/pac-service/src/integrations/meyer/README.md"
>
查看当前采集说明
</a></aside><a
class=
"skip-link"
href=
"#main"
>
跳到正文
</a><div
class=
"wrap"
id=
"top"
>
<header
class=
"document-header"
><div
class=
"topline"
><div
class=
"eyebrow"
>
美亚 × FridaySaaS × PAC
</div><div
class=
"tools"
aria-label=
"文档工具"
><button
type=
"button"
id=
"theme-button"
aria-label=
"切换深浅色"
aria-pressed=
"false"
title=
"切换深浅色"
><svg
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.6"
aria-hidden=
"true"
><circle
cx=
"12"
cy=
"12"
r=
"8"
/><path
d=
"M12 4a8 8 0 0 1 0 16Z"
fill=
"currentColor"
stroke=
"none"
/></svg><span>
深浅色
</span></button><button
type=
"button"
id=
"print-button"
aria-label=
"打印或另存为 PDF"
title=
"打印或另存为 PDF"
><svg
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"1.5"
aria-hidden=
"true"
><path
d=
"M6 9V3h12v6M6 18H3v-8h18v8h-3M6 14h12v7H6Z"
/></svg><span>
打印
</span></button></div></div><h1><span>
影像与分析数据
</span><span>
方案设计
</span></h1><div
class=
"meta"
><span
class=
"version"
>
评审修订稿
</span><span>
v0.4
</span><span
aria-hidden=
"true"
>
·
</span><time
datetime=
"2026-10-19"
>
2026-10-19
</time></div><p
class=
"scope"
>
本稿确认方案方向与系统边界,具体实现另行设计。
</p></header>
<main
id=
"main"
><div
class=
"opening"
><p
class=
"boundary"
><strong>
整体边界:美亚管理影像资产并提供影像算法能力,PAC 承担患者分析层,FridaySaaS 承担应用消费层。PAC 直接对接美亚的授权和数据接口,向 FridaySaaS 开放统一的患者分析能力。
</strong></p><p
class=
"principle"
>
判断依据是:
<strong>
数据由各自的权威系统负责,分析由 PAC 统一组织,应用围绕结果开展业务。
</strong>
FridaySaaS 定义业务患者和组织归属,美亚维护影像资产及绑定关系,PAC 校验对应关系、留存依据并形成分析。三个系统可以使用同一份关联,但不能各自猜测患者身份或独立修正归属。
</p></div><nav
class=
"contents"
id=
"contents"
aria-label=
"文档目录"
><span
class=
"contents-label"
>
目录
</span><div
class=
"contents-links"
><a
href=
"#relationship"
><span>
01
</span>
整体关系
</a><a
href=
"#meyer"
><span>
02
</span>
美亚
</a><a
href=
"#saas"
><span>
03
</span>
FridaySaaS
</a><a
href=
"#pac"
><span>
04
</span>
PAC
</a><a
href=
"#review"
><span>
05
</span>
当前方案与范围
</a><a
href=
"#sources"
><span>
附
</span>
依据与范围
</a></div></nav><section
class=
"chapter"
id=
"relationship"
aria-labelledby=
"heading-1"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
01
</span><h2
id=
"heading-1"
>
整体关系:采集呈现、患者分析、业务应用分工
</h2></div><p>
FridaySaaS 沿用现有美亚接入,同步患者、诊所和就诊信息,配合影像采集并提供阅片入口。PAC 直接获取美亚影像与分析,结合病历、治疗等患者事实,向 SaaS 输出分析能力。
</p><figure
aria-labelledby=
"flow-caption"
class=
"flow"
><figcaption
id=
"flow-caption"
><span>
图 01
</span>
采集、分析与应用的关系
<small>
目标链路 · 美亚新影像启用时
</small></figcaption>
<svg
aria-labelledby=
"diagram-title diagram-desc"
class=
"diagram-desktop"
role=
"img"
viewbox=
"0 45 840 380"
>
<title
id=
"diagram-title"
>
美亚、FridaySaaS 与 PAC 的职责及数据流
</title><desc
id=
"diagram-desc"
>
诊所设备向美亚采集影像,美亚使用 OSS 存储原片。FridaySaaS 向美亚同步患者、诊所与就诊,向 PAC 提供患者身份与业务数据。美亚向 PAC 实时推送新增影像通知。PAC 直接授权访问美亚影像、批量查询分析,并向 SaaS 输出分析结果、依据与状态。
</desc>
<defs><marker
id=
"arrow"
markerheight=
"6"
markerwidth=
"6"
orient=
"auto-start-reverse"
refx=
"9"
refy=
"5"
viewbox=
"0 0 10 10"
><path
d=
"M1 1L9 5L1 9"
fill=
"none"
stroke=
"var(--brand)"
stroke-width=
"1.5"
></path></marker></defs>
<path
class=
"edge"
d=
"M180 115H325"
></path><path
class=
"edge"
d=
"M515 115H660"
></path>
<text
class=
"label"
text-anchor=
"middle"
x=
"252"
y=
"101"
>
影像采集
</text><text
class=
"label"
text-anchor=
"middle"
x=
"588"
y=
"101"
>
原片存储
</text>
<path
class=
"edge"
d=
"M175 300L365 160"
></path><path
class=
"edge"
d=
"M625 300L435 160"
></path><path
class=
"edge"
d=
"M510 160L700 300"
></path>
<text
class=
"label"
text-anchor=
"middle"
x=
"183"
y=
"218"
><tspan
x=
"183"
>
同步患者、诊所
</tspan><tspan
dy=
"20"
x=
"183"
>
与就诊
</tspan></text>
<text
class=
"label"
text-anchor=
"middle"
x=
"430"
y=
"240"
><tspan
x=
"430"
>
授权访问影像
</tspan><tspan
dy=
"20"
x=
"430"
>
批量查询分析
</tspan></text><text
class=
"label"
text-anchor=
"middle"
x=
"716"
y=
"208"
><tspan
x=
"716"
>
实时推送
</tspan><tspan
dy=
"20"
x=
"716"
>
新增影像通知
</tspan></text>
<path
class=
"edge"
d=
"M290 325H550"
></path><path
class=
"edge"
d=
"M550 382H290"
></path>
<text
class=
"label"
text-anchor=
"middle"
x=
"420"
y=
"311"
>
患者身份与业务数据
</text><text
class=
"label"
text-anchor=
"middle"
x=
"420"
y=
"368"
>
分析结果、依据与状态
</text>
<rect
class=
"node"
height=
"90"
rx=
"6"
width=
"160"
x=
"20"
y=
"70"
></rect><text
class=
"title"
style=
"font-size:16px"
text-anchor=
"middle"
x=
"100"
y=
"110"
>
诊所设备
</text><text
class=
"sub"
text-anchor=
"middle"
x=
"100"
y=
"135"
>
与前置机
</text>
<rect
class=
"node"
height=
"90"
rx=
"6"
width=
"190"
x=
"325"
y=
"70"
></rect><text
class=
"title"
text-anchor=
"middle"
x=
"420"
y=
"110"
>
美亚 PACS
</text><text
class=
"sub"
text-anchor=
"middle"
x=
"420"
y=
"135"
>
影像资产 · 阅片 · 影像算法
</text>
<rect
class=
"node"
height=
"90"
rx=
"6"
width=
"160"
x=
"660"
y=
"70"
></rect><text
class=
"title"
text-anchor=
"middle"
x=
"740"
y=
"110"
>
OSS
</text><text
class=
"sub"
text-anchor=
"middle"
x=
"740"
y=
"135"
>
原片存储
</text>
<rect
class=
"node"
height=
"102"
rx=
"6"
width=
"230"
x=
"60"
y=
"300"
></rect><text
class=
"title"
text-anchor=
"middle"
x=
"175"
y=
"344"
>
FridaySaaS
</text><text
class=
"sub"
text-anchor=
"middle"
x=
"175"
y=
"372"
>
业务管理 · 影像呈现 · 应用消费
</text>
<rect
class=
"node hub"
height=
"102"
rx=
"6"
width=
"230"
x=
"550"
y=
"300"
></rect><text
class=
"title"
text-anchor=
"middle"
x=
"665"
y=
"344"
>
PAC
</text><text
class=
"sub"
text-anchor=
"middle"
x=
"665"
y=
"372"
>
数据接入 · 患者分析 · 开放输出
</text>
</svg>
<div
aria-label=
"系统关系"
class=
"diagram-mobile"
><div
class=
"route"
><strong>
诊所设备 → 美亚 → OSS
</strong><span>
采集影像,管理与存储原片
</span></div><div
class=
"route"
><strong>
FridaySaaS → 美亚
</strong><span>
同步患者、诊所与就诊
</span></div><div
class=
"route"
><strong>
FridaySaaS → PAC
</strong><span>
提供患者身份与业务数据
</span></div><div
class=
"route"
><strong>
美亚 → PAC
</strong><span>
实时推送新增影像通知
</span></div><div
class=
"route"
><strong>
PAC → 美亚
</strong><span>
授权访问影像,批量查询分析
</span></div><div
class=
"route"
><strong>
PAC → FridaySaaS
</strong><span>
输出分析结果、依据与状态
</span></div></div></figure><p>
FridaySaaS 在患者详情、接诊、客服等场景使用 PAC 的分析。
<strong>
召回只是其中一个场景,患者是否进入召回计划不应决定其能否获得分析。
</strong></p><p>
查看原片继续走 SaaS 阅片入口,PAC 工作台可沿用宿主槽位;PAC 为分析取片则直接访问美亚。
<strong>
如果分析依赖用户打开页面,没人查看的影像就可能被漏掉;如果每次打开页面才取数并计算,响应又会受外部服务和算法耗时影响。
</strong>
因此,后台持续获取和分析,页面按需读取结果、查看原片。
</p></section><section
class=
"chapter"
id=
"meyer"
aria-labelledby=
"heading-2"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
02
</span><h2
id=
"heading-2"
>
美亚:实时通知新增影像,分析结果延后获取
</h2></div><p>
根据美亚联调回复及后续沟通,已确认:
</p><div
class=
"table-wrap"
><table
class=
"two-col"
>
<thead>
<tr>
<th
scope=
"col"
>
已确认信息
</th>
<th
scope=
"col"
>
对方案的影响
</th>
</tr>
</thead>
<tbody>
<tr>
<td
data-label=
"已确认信息"
>
影像数据只创建,不修改、不删除
</td>
<td
data-label=
"对方案的影响"
>
按新增采集与去重,不设计影像修改、删除的同步链路
</td>
</tr>
<tr>
<td
data-label=
"已确认信息"
>
影像生成后实时推送,接收方提供回调地址
</td>
<td
data-label=
"对方案的影响"
>
PAC 提供回调入口,直接接收新增影像通知
</td>
</tr>
<tr>
<td
data-label=
"已确认信息"
>
上传后后台识别病症,通常约 3–5 分钟,高峰预计仍为分钟级
</td>
<td
data-label=
"对方案的影响"
>
影像通知与分析结果并非同时到达;该时间不是完成时限承诺
</td>
</tr>
<tr>
<td
data-label=
"已确认信息"
>
分析接口将支持批量查询;正常查询无数据时,美亚说明算法仍在处理中
</td>
<td
data-label=
"对方案的影响"
>
批量获取结果,未取得结果的后续补拉,不要求完整分析状态接口
</td>
</tr>
<tr>
<td
data-label=
"已确认信息"
>
病症分析目前仅支持全景片,CBCT 暂不支持
</td>
<td
data-label=
"对方案的影响"
>
对不支持的类型不反复补拉病症分析
</td>
</tr>
</tbody>
</table></div><p><strong>
实时通知解决“发现新影像”,延后补拉解决“取得晚到分析”。
</strong>
如果收到通知时只查一次,尚未生成的分析就可能被漏掉;没有取得结果,也不能解释为没有发现问题。
</p></section><section
class=
"chapter"
id=
"saas"
aria-labelledby=
"heading-3"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
03
</span><h2
id=
"heading-3"
>
FridaySaaS:做好业务身份、影像呈现和分析消费
</h2></div><p>
SaaS 负责采集接入和呈现,向 PAC 提供患者业务数据及授权上下文,再消费分析结果。
<strong>
无需为了转交 PAC,再建设一套美亚分析采集和重推链路。
</strong>
否则同一份分析要经过两套同步过程,既重复维护,也增加故障定位的环节。
</p><p>
SaaS 保留自身业务所需的关联和操作记录。医生采纳建议、客服据此跟进时,应记录引用的分析版本及必要内容。
<strong>
否则分析更新后,就难以还原当时为什么做出那次业务操作。
</strong>
分析依据和版本由 PAC 管理,业务执行由 SaaS 管理。
</p><p>
新老影像功能可以保留,实际择一启用:旧影像沿用 SaaS 数据通路,美亚影像由 PAC 直连获取,来源差异由 PAC 接入层统一。
<strong>
应用不应随影像来源切换而重新适配;同一张片迁入美亚,也不应被当成两份独立依据。
</strong></p></section><section
class=
"chapter"
id=
"pac"
aria-labelledby=
"heading-4"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
04
</span><h2
id=
"heading-4"
>
PAC:统一分析,留存依据,对应用负责
</h2></div><h3>
① 接入归一,身份与权限沿用业务边界
</h3><p>
美亚接入由 PAC 独立适配层负责,转换为 PAC 标准数据后用于分析。患者对应关系以 SaaS 业务身份和美亚绑定为依据,缺失或冲突时不猜测匹配。
</p><p>
PAC 负责美亚服务授权;用户能查看哪些患者和分析,仍依据 SaaS 的业务授权,由 PAC 校验执行。
<strong>
服务能读取多家诊所,不代表每个调用方都有权访问这些患者。
</strong></p><h3>
② 留存分析依据,原片继续由美亚管理
</h3><p>
PAC 保存影像关联、原始分析依据和自身分析结果,并保留来源、版本及数据时效。
<strong>
如果每次都临时向美亚取结果,同一份业务数据重放时可能取得不同内容,就无法解释当时为什么得出那个判断。
</strong></p><p>
原片仍存于美亚/OSS,由 PAC 按需授权访问。美亚暂时不可用时,PAC 仍能提供已留存的结果及依据,并说明数据截至时间;原片访问和最新分析获取则可能受影响。
</p><h3>
③ 区分来源,开放统一的患者分析能力
</h3><p>
PAC 可以使用美亚已有的算法结果,也可按需要获取原片、调用其他分析服务,再结合患者事实形成综合分析。承担分析层不要求底层影像算法全部自研。
</p><p><strong>
美亚发现了什么、PAC 综合判断了什么、医生确认了什么,应分别标明来源和依据。
</strong>
否则算法发现容易被误当成医生诊断,原始结果变化时也难以追溯受影响的结论。
</p><p>
对已接入且获授权的患者,PAC 向应用提供分析结论、依据、版本和状态。
<strong>
未取得分析结果不代表没有发现问题;PAC 只表达已知的结果可用性与能力范围,不推断美亚内部处理状态。
</strong>
SaaS 使用这些结果开展业务;结果回流时也应保留来源,避免同一结论被重复当成独立依据。
</p></section><section
class=
"chapter"
id=
"review"
aria-labelledby=
"heading-5"
><div
class=
"chapter-head"
><span
class=
"chapter-num"
>
05
</span><h2
id=
"heading-5"
>
当前方案与范围
</h2></div><p><strong>
新增影像实时通知 PAC → 留存影像记录 → 批量查询分析 → 有结果保存,无结果延后补拉。
</strong>
回调确认以通知可靠落库为界,不等待分析完成。接入前的历史影像另做首次采集。
</p></section><aside
class=
"sources"
id=
"sources"
aria-labelledby=
"sources-title"
><h2
id=
"sources-title"
>
依据与范围
</h2><p>
本稿依据本轮评审原则、2026-10-19 美亚联调回复及后续确认,以及既有代码和本地联调记录。直连取数已有验证基础,完整分析服务仍需后续设计与实现。
</p><p>
参考资料:
<span
title=
"本机参考文件,未随归档分发"
>
系统集成方案
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
私有化部署方案
</span>
、
<span
title=
"本机参考文件,未随归档分发"
>
接口对接文档
</span>
。
</p><p>
参考资料链接指向本机原件;分享本文时可一并提供原 PDF。
</p></aside></main><footer
class=
"page-footer"
><span>
美亚 × FridaySaaS × PAC · 影像与分析数据方案设计
</span><a
href=
"#top"
>
评审修订稿 v0.4 · 返回页首 ↑
</a></footer></div><a
class=
"back-top"
href=
"#contents"
aria-label=
"返回文档目录"
>
目录 ↑
</a>
<script>
(()
=>
{
const
root
=
document
.
documentElement
,
themeButton
=
document
.
getElementById
(
'theme-button'
),
key
=
'imaging-document-theme'
;
const
scheme
=
matchMedia
(
'(prefers-color-scheme: dark)'
);
const
isDark
=
()
=>
root
.
dataset
.
theme
?
root
.
dataset
.
theme
===
'dark'
:
scheme
.
matches
;
const
update
=
()
=>
themeButton
.
setAttribute
(
'aria-pressed'
,
String
(
isDark
()));
try
{
const
saved
=
localStorage
.
getItem
(
key
);
if
(
saved
===
'dark'
||
saved
===
'light'
)
root
.
dataset
.
theme
=
saved
;}
catch
{}
update
();
scheme
.
addEventListener
(
'change'
,
update
);
themeButton
.
addEventListener
(
'click'
,()
=>
{
const
theme
=
isDark
()?
'light'
:
'dark'
;
root
.
dataset
.
theme
=
theme
;
try
{
localStorage
.
setItem
(
key
,
theme
)}
catch
{}
update
()});
document
.
getElementById
(
'print-button'
).
addEventListener
(
'click'
,()
=>
window
.
print
());
const
back
=
document
.
querySelector
(
'.back-top'
);
const
check
=
()
=>
{
const
visible
=
scrollY
>
700
;
back
.
classList
.
toggle
(
'visible'
,
visible
);
back
.
tabIndex
=
visible
?
0
:
-
1
;
back
.
setAttribute
(
'aria-hidden'
,
String
(
!
visible
))};
addEventListener
(
'scroll'
,
check
,{
passive
:
true
});
check
();
})();
</script></body></html>
docs/_archive/2026-10-operations-and-design/2026-10-19-meyer-friday-pac-imaging-review.md
0 → 100644
View file @
15d08d44
# 美亚 × FridaySaaS × PAC:影像与分析数据方案设计
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。 原方案中的影像事件推送未被采用,当前实现按诊所分页轮询并批量查询分析。
> 评审修订稿 v0.4 · 2026-10-19
> 本稿确认方案方向与系统边界,具体实现另行设计。
**整体边界:美亚管理影像资产并提供影像算法能力,PAC 承担患者分析层,FridaySaaS 承担应用消费层。PAC 直接对接美亚的授权和数据接口,向 FridaySaaS 开放统一的患者分析能力。**
判断依据是:
**数据由各自的权威系统负责,分析由 PAC 统一组织,应用围绕结果开展业务。**
FridaySaaS 定义业务患者和组织归属,美亚维护影像资产及绑定关系,PAC 校验对应关系、留存依据并形成分析。三个系统可以使用同一份关联,但不能各自猜测患者身份或独立修正归属。
## 1. 整体关系:采集呈现、患者分析、业务应用分工
FridaySaaS 沿用现有美亚接入,同步患者、诊所和就诊信息,配合影像采集并提供阅片入口。PAC 直接获取美亚影像与分析,结合病历、治疗等患者事实,向 SaaS 输出分析能力。
```
mermaid
flowchart TB
E[诊所设备与前置机] --> M["美亚 PACS<br/>影像资产、阅片、影像算法"]
M --> O[OSS 原片存储]
S["FridaySaaS<br/>业务管理、影像呈现、应用消费"] -->|同步患者、诊所与就诊| M
S -->|患者身份与业务数据| P["PAC<br/>数据接入、患者分析、开放输出"]
M -->|实时推送新增影像通知| P
P -->|直接授权访问影像、批量查询分析| M
P -->|分析结果、依据与状态| S
```
FridaySaaS 在患者详情、接诊、客服等场景使用 PAC 的分析。
**召回只是其中一个场景,患者是否进入召回计划不应决定其能否获得分析。**
查看原片继续走 SaaS 阅片入口,PAC 工作台可沿用宿主槽位;PAC 为分析取片则直接访问美亚。
**如果分析依赖用户打开页面,没人查看的影像就可能被漏掉;如果每次打开页面才取数并计算,响应又会受外部服务和算法耗时影响。**
因此,后台持续获取和分析,页面按需读取结果、查看原片。
## 2. 美亚:实时通知新增影像,分析结果延后获取
根据美亚联调回复及后续沟通,已确认:
| 已确认信息 | 对方案的影响 |
|---|---|
| 影像数据只创建,不修改、不删除 | 按新增采集与去重,不设计影像修改、删除的同步链路 |
| 影像生成后实时推送,接收方提供回调地址 | PAC 提供回调入口,直接接收新增影像通知 |
| 上传后后台识别病症,通常约 3–5 分钟,高峰预计仍为分钟级 | 影像通知与分析结果并非同时到达;该时间不是完成时限承诺 |
| 分析接口将支持批量查询;正常查询无数据时,美亚说明算法仍在处理中 | 批量获取结果,未取得结果的后续补拉,不要求完整分析状态接口 |
| 病症分析目前仅支持全景片,CBCT 暂不支持 | 对不支持的类型不反复补拉病症分析 |
**实时通知解决“发现新影像”,延后补拉解决“取得晚到分析”。**
如果收到通知时只查一次,尚未生成的分析就可能被漏掉;没有取得结果,也不能解释为没有发现问题。
## 3. FridaySaaS:做好业务身份、影像呈现和分析消费
SaaS 负责采集接入和呈现,向 PAC 提供患者业务数据及授权上下文,再消费分析结果。
**无需为了转交 PAC,再建设一套美亚分析采集和重推链路。**
否则同一份分析要经过两套同步过程,既重复维护,也增加故障定位的环节。
SaaS 保留自身业务所需的关联和操作记录。医生采纳建议、客服据此跟进时,应记录引用的分析版本及必要内容。
**否则分析更新后,就难以还原当时为什么做出那次业务操作。**
分析依据和版本由 PAC 管理,业务执行由 SaaS 管理。
新老影像功能可以保留,实际择一启用:旧影像沿用 SaaS 数据通路,美亚影像由 PAC 直连获取,来源差异由 PAC 接入层统一。
**应用不应随影像来源切换而重新适配;同一张片迁入美亚,也不应被当成两份独立依据。**
## 4. PAC:统一分析,留存依据,对应用负责
**① 接入归一,身份与权限沿用业务边界**
美亚接入由 PAC 独立适配层负责,转换为 PAC 标准数据后用于分析。患者对应关系以 SaaS 业务身份和美亚绑定为依据,缺失或冲突时不猜测匹配。
PAC 负责美亚服务授权;用户能查看哪些患者和分析,仍依据 SaaS 的业务授权,由 PAC 校验执行。
**服务能读取多家诊所,不代表每个调用方都有权访问这些患者。**
**② 留存分析依据,原片继续由美亚管理**
PAC 保存影像关联、原始分析依据和自身分析结果,并保留来源、版本及数据时效。
**如果每次都临时向美亚取结果,同一份业务数据重放时可能取得不同内容,就无法解释当时为什么得出那个判断。**
原片仍存于美亚/OSS,由 PAC 按需授权访问。美亚暂时不可用时,PAC 仍能提供已留存的结果及依据,并说明数据截至时间;原片访问和最新分析获取则可能受影响。
**③ 区分来源,开放统一的患者分析能力**
PAC 可以使用美亚已有的算法结果,也可按需要获取原片、调用其他分析服务,再结合患者事实形成综合分析。承担分析层不要求底层影像算法全部自研。
**美亚发现了什么、PAC 综合判断了什么、医生确认了什么,应分别标明来源和依据。**
否则算法发现容易被误当成医生诊断,原始结果变化时也难以追溯受影响的结论。
对已接入且获授权的患者,PAC 向应用提供分析结论、依据、版本和状态。
**未取得分析结果不代表没有发现问题;PAC 只表达已知的结果可用性与能力范围,不推断美亚内部处理状态。**
SaaS 使用这些结果开展业务;结果回流时也应保留来源,避免同一结论被重复当成独立依据。
## 5. 当前方案与范围
**新增影像实时通知 PAC → 留存影像记录 → 批量查询分析 → 有结果保存,无结果延后补拉。**
回调确认以通知可靠落库为界,不等待分析完成。接入前的历史影像另做首次采集。
---
### 依据与范围
本稿依据本轮评审原则、2026-10-19 美亚联调回复及后续确认,以及既有代码和本地联调记录。直连取数已有验证基础,完整分析服务仍需后续设计与实现。
参考资料:系统集成方案(本机参考文件
`瑞尔口腔影像中心PACS项目系统集成方案v0.4.pdf`
,未随归档分发)、私有化部署方案(本机参考文件
`瑞尔PACS影像中心私有化部署方案v0.1.pdf`
,未随归档分发)、接口对接文档(本机参考文件
`PACS_瑞尔接口对接文档.pdf`
,未随归档分发)。
docs/_archive/2026-10-operations-and-design/2026-10-20-data-warehouse-and-pac-positioning.md
0 → 100644
View file @
15d08d44
# 公司数仓的定位与 PAC 的职责边界
> 历史归档(2026-10-22):保留原记录日期的方案、样本和验收结论,不代表当前运行状态;现行入口见本目录索引。
> 架构讨论稿 · 2026-10-20
**公司需要独立、可复用的数据能力。以 PAC 兼任公司级数据底座,是满足当前业务的过渡方案,不能作为长期的公司数仓定位。**
过渡的是 PAC 代为承担的公共数据职责;患者分析、行动判断与执行协作,仍然是 PAC 可以长期发展的产品能力。
## 1. 数仓是什么:公司的公共数据基础
数仓持续整合不同系统、不同业务的数据,形成
**定义一致、来源可追溯、质量可核对、能够重复使用**
的公共数据,服务经营分析、报表、算法和业务应用。
它的价值体现在三个方面:
-
**整合**
:患者、诊疗、收费、营销、人员、门店等数据能够关联使用。
-
**一致**
:身份、时间、状态和指标有明确口径,减少各部门各算一套。
-
**复用**
:一次可靠接入和公共加工,可以支持多个消费者。
数仓需要独立的数据模型和治理责任。把业务库集中复制到另一个数据库,只完成了数据汇集;还需要整合语义、明确质量和使用规则。
业务系统继续维护自己的记录并办理业务。数仓通常通过同步取得数据,不要求预约、收费、写病历等日常操作经过数仓。
## 2. 公司为什么需要:让数据服务多个业务
公司需要回答的问题已经超出单一患者场景:渠道带来的患者是否到诊、治疗和收费如何衔接、不同门店和人员的表现是否可比。这些问题需要跨系统的数据和共同口径。
如果每个产品独立取数、清洗和解释,同一份数据就会产生多套加工逻辑。新增应用、源系统变更和历史补数,都会重复付出成本;结果不一致时,也难以确定谁负责。
**建设时机取决于复用和治理需求,不只取决于数据量。**
多个消费者反复使用同一批数据、口径冲突持续出现、跨系统分析成为日常工作时,就应明确公共数据能力的建设责任。
这不意味着立即建设覆盖全公司的大型平台。可以从已有重复需求开始,逐步形成公共能力;前提是有人持续负责定义、质量和交付。
## 3. PAC 当前承担了什么:为患者应用补齐数据条件
PAC 已包含多源接入、字段与语义归一、来源留存、事实版本、画像及计划更新等能力,具备部分数据仓储与治理特征。当前主要业务落点是客户召回。
这些能力首先服务于 PAC:把已接入的患者记录转化为可解释的事实,支持画像、机会判断、工作分配和跟进。
**有数据加工和事实存储,不等于具备公司级数仓的覆盖范围与服务责任。**
PAC 中某条记录可供召回判断,也不意味着它已经满足财务统计、经营分析或其他产品的全部要求。
## 4. 为什么只能过渡:应用边界不能替代公司数据边界
| 局限 | 对公司的影响 |
|---|---|
| 数据覆盖随患者应用需求选择 | PAC 已接入的数据,不能代表全公司的业务数据已完整覆盖 |
| 数据语义和加工围绕患者判断组织 | 其他业务仍需核对粒度、状态与统计口径,不能直接照用应用结果 |
| 原文与版本留存在已采集范围内 | 重放可以重处理已有记录,无法恢复从未接入的数据 |
| 数据交付与应用运行相互耦合 | 多个消费者长期依赖后,公共补数、质量和变更管理会挤占产品迭代,并扩大故障影响 |
继续无边界扩充 PAC,会使其同时承担公共数据供应和患者业务交付。两类责任的消费者、覆盖范围、质量要求和变更节奏不同,需要分别管理。
## 5. 长期边界:记录、公共数据与业务判断分别负责
| 角色 | 核心责任 |
|---|---|
|
**源业务系统**
| 维护权威业务记录,办理业务操作,明确数据及其变更含义 |
|
**公司数仓/公共数据能力**
| 可靠整合、公共身份与口径、历史留存、来源追溯、质量核对及共享数据交付 |
|
**PAC**
| 患者事实的领域解释、画像、机会与优先级判断、行动计划、分配和反馈 |
“单一可信源”应落实为明确的权威分工:业务事实以对应源系统为准,公共分析使用经过治理的数据和指标,PAC 的判断与行动状态由 PAC 管理。无需把所有读写集中到同一个数据库。
公共数据模型应表达可复用的业务含义;源系统差异在接入与加工中处理,应用特有判断留在应用边界内。公共模型可以演进,但不应随某个来源的表结构或某个应用的临时需求任意变化。
数据完整性和正确性也需要分工:源系统负责提供明确、可靠的记录,公共数据能力负责同步与加工质量,PAC 负责自身判断及证据使用。数仓无法自动消除源端缺失或含义不明的问题。
## 6. 过渡原则:沉淀公共能力,保留产品价值
-
**当前可继续交付**
:PAC 在明确数据范围和质量条件下提供患者分析与召回能力,不必等待完整数仓建成。
-
**公共能力逐步独立**
:优先沉淀被多个消费者重复需要的接入、归一、追溯和质量核对,明确责任人与交付约定。
-
**迁移按数据范围推进**
:公共数据具备可验证的完整性、时效和语义后,PAC 再逐步切换,避免同一数据长期存在相互冲突的加工路径。
-
**产品职责持续保留**
:数仓完善后,PAC 仍维护自己的分析、计划和执行状态,并对患者相关判断负责。
外部公司的同类数据也按此原则接入:主要供 PAC 使用时,可以直接进入其接入流程;需要跨产品复用时,应纳入公共数据能力,并保留来源、授权范围及组织隔离。
**目标是让公司获得稳定的数据资产,让 PAC 集中解决患者相关的判断与行动问题。是否独立部署、采用何种数据库及如何建表,属于后续设计。**
---
### 依据
-
当前 PAC 的
[
架构总览
](
../../../apps/pac-docs/content/docs/architecture/overview.mdx
)
、
[
三层模型
](
../../../apps/pac-docs/content/docs/architecture/three-layer-model.mdx
)
与
[
产品概述
](
../../../apps/pac-docs/content/docs/start/what-is-pac.mdx
)
。
-
[
DataWorks 数仓分层
](
https://www.alibabacloud.com/help/en/dataworks/user-guide/data-warehouse-layering
)
:源数据、公共加工与应用服务的职责区分。
docs/_archive/2026-10-operations-and-design/README.md
0 → 100644
View file @
15d08d44
# 2026 年 10 月运维验收与架构讨论归档
2026-10-22 整理。保存 11 份独有文档,包含历史发布记录、数仓定位讨论及影像方案的不同评审版本。
## 现行入口
-
[
美亚采集器
](
../../../apps/pac-service/src/integrations/meyer/README.md
)
-
[
患者事实查询 API
](
../../../apps/pac-docs/content/docs/api/open/PatientFactsLookupController_lookup.mdx
)
-
[
PAC 产品概述
](
../../../apps/pac-docs/content/docs/start/what-is-pac.mdx
)
-
[
运维说明
](
../../operations/README.md
)
-
[
美亚与患者事实开发历史
](
../2026-10-meyer-and-patient-facts/README.md
)
## 归档清单
| 文件 | 内容 |
| --- | --- |
|
[
2026-10-14-push-latency-local.md
](
2026-10-14-push-latency-local.md
)
| Push 优化及测试、生产验收;含此前未提交的生产验收补充 |
|
[
2026-10-14-return-visit-push-fix-test.md
](
2026-10-14-return-visit-push-fix-test.md
)
| 回访推送修复、单条重放和发布验收 |
|
[
2026-10-18-business-date-shanghai-test-validation.md
](
2026-10-18-business-date-shanghai-test-validation.md
)
| 北京业务日期修复及跨时区验证 |
|
[
2026-10-18-jvs-complaint-ingestion-test-validation.md
](
2026-10-18-jvs-complaint-ingestion-test-validation.md
)
| 投诉摄入测试发布与真实样本验收 |
|
[
2026-10-18-jvs-complaint-ingestion-prod-validation.md
](
2026-10-18-jvs-complaint-ingestion-prod-validation.md
)
| 投诉摄入生产发布与真实样本验收 |
|
[
2026-10-20-data-warehouse-and-pac-positioning.md
](
2026-10-20-data-warehouse-and-pac-positioning.md
)
| 数仓与 PAC 的职责边界讨论 |
|
[
2026-10-16-meyer-friday-pac-imaging-review.md
](
2026-10-16-meyer-friday-pac-imaging-review.md
)
| 影像早期评审稿 v0.2 |
|
[
2026-10-16-meyer-friday-pac-imaging-review-document.html
](
2026-10-16-meyer-friday-pac-imaging-review-document.html
)
| v0.2 独立文档版 HTML |
|
[
2026-10-16-meyer-friday-pac-imaging-review.html
](
2026-10-16-meyer-friday-pac-imaging-review.html
)
| v0.2 展示版 HTML |
|
[
2026-10-19-meyer-friday-pac-imaging-review.md
](
2026-10-19-meyer-friday-pac-imaging-review.md
)
| 影像修订评审稿 v0.4 |
|
[
2026-10-19-meyer-friday-pac-imaging-review-document.html
](
2026-10-19-meyer-friday-pac-imaging-review-document.html
)
| v0.4 独立文档版 HTML |
## 阅读边界
-
环境状态、版本、数据量、耗时和发布结果均为原记录日期的证据;本次没有重新查询服务器。
-
两版影像方案保存了不同阶段的职责划分,早期事件推送建议未被采用。当前按诊所分页轮询影像、批量查询分析,具体行为以现行代码为准。
-
数仓文档是架构讨论,不表示公司数仓已经建成,也不改变 PAC 现有业务职责。
-
不在此复制患者查询原文、临时访问地址、生产备份或运行凭据。内部联调资料单独备份,不纳入 Git。
-
原本机 PDF 及外部仓库源码仅保留引用说明,未作为附件发布;本仓库链接已按新位置修正。
[
移动与校验清单
](
manifest.json
)
记录原路径、新路径及整理前后摘要。原始文档另有完整可恢复备份。
docs/_archive/2026-10-operations-and-design/manifest.json
0 → 100644
View file @
15d08d44
{
"archived_on"
:
"2026-10-22"
,
"files"
:
[
{
"from"
:
"docs/qa/2026-10-14-push-latency-local.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-14-push-latency-local.md"
,
"original_sha256"
:
"9dbe13cb926a54834dcb35a4d013f6bb4297e46668cd40d91beb47cc83a7eb73"
,
"archived_sha256"
:
"ad369f52632c4f7b118429f78864a6e19594f864072297cd4c8c0ef5e59f63a5"
,
"category"
:
"historical_validation"
},
{
"from"
:
"docs/qa/2026-10-14-return-visit-push-fix-test.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-14-return-visit-push-fix-test.md"
,
"original_sha256"
:
"6e92dfeeade2ce23c28b0a5e09a0429c38453ba6efadb6d5c27c8381dd82a162"
,
"archived_sha256"
:
"fa0d03f1009093bd744810c38adb9c5c2c3911cc93ba030007a582d071b18bb5"
,
"category"
:
"historical_validation"
},
{
"from"
:
"docs/qa/2026-10-16-meyer-friday-pac-imaging-review-document.html"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review-document.html"
,
"original_sha256"
:
"794cd8b72fb6fd75259f4cb63715d1f6f95e9f3e31ea555f43b1c6766a1373de"
,
"archived_sha256"
:
"a3387fdf95b8156838b06a875d4c35cd2a5138010b6f44bb7087999a0f0cdf40"
,
"category"
:
"historical_design"
},
{
"from"
:
"docs/qa/2026-10-16-meyer-friday-pac-imaging-review.html"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review.html"
,
"original_sha256"
:
"0adb5e5adfe6674ca7cd15b0f62e12407b57e606628a1074c139e21c4c250765"
,
"archived_sha256"
:
"b9bcbb7d3fff38d3bd22c33191d3106653e37f128a5d8e8c242c7fd41fa6c467"
,
"category"
:
"historical_design"
},
{
"from"
:
"docs/qa/2026-10-16-meyer-friday-pac-imaging-review.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-16-meyer-friday-pac-imaging-review.md"
,
"original_sha256"
:
"2679e80c6546802bc9d29d130a684e2b3696e1b86c79a85c5ccff92808f2332d"
,
"archived_sha256"
:
"e659a0769012faa8c6e090e0dc622b0a5f86dd9adcab9c4fe6c206474b759879"
,
"category"
:
"historical_design"
},
{
"from"
:
"docs/qa/2026-10-18-business-date-shanghai-test-validation.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-18-business-date-shanghai-test-validation.md"
,
"original_sha256"
:
"fff7acd4aea01673487cc8b2e96b299d877081412f3e619bdd85d69ab0a26b32"
,
"archived_sha256"
:
"99be312617b0445866ea5e193d9708658940a862adf4db4d9af6c113eb98ade7"
,
"category"
:
"historical_validation"
},
{
"from"
:
"docs/qa/2026-10-18-jvs-complaint-ingestion-prod-validation.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-18-jvs-complaint-ingestion-prod-validation.md"
,
"original_sha256"
:
"ddee388593323a72b30d75388f534767db88370824b362f40d7946f7d0302edc"
,
"archived_sha256"
:
"64af94eb97d52140f76113c28be8193a34a9236cf930e228b0ebf9822611ecc5"
,
"category"
:
"historical_validation"
},
{
"from"
:
"docs/qa/2026-10-18-jvs-complaint-ingestion-test-validation.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-18-jvs-complaint-ingestion-test-validation.md"
,
"original_sha256"
:
"ba7149068b9b7e9fe7e9dde3e40d47c464007e156e72d542da3b95dee09cc64f"
,
"archived_sha256"
:
"8c3116cddafd0d03464183beccd0841a062ceda2f44d763a2afd44a18baac42f"
,
"category"
:
"historical_validation"
},
{
"from"
:
"docs/qa/2026-10-19-meyer-friday-pac-imaging-review-document.html"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-19-meyer-friday-pac-imaging-review-document.html"
,
"original_sha256"
:
"aebf50c50fd912e51c4aeea1d1dd78aa1b80658d61f1c73fe4889794ea7a78a3"
,
"archived_sha256"
:
"c9541bf669f483732ed9ae7bec8fd597c1aae9087954dad17ff5a803256fd64b"
,
"category"
:
"historical_design"
},
{
"from"
:
"docs/qa/2026-10-19-meyer-friday-pac-imaging-review.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-19-meyer-friday-pac-imaging-review.md"
,
"original_sha256"
:
"b85aaa9b8fc67efea031156935ede484e8975af30bfee0de481c88cfb4d57e47"
,
"archived_sha256"
:
"4cef9b11cfdd1ddaf40c5526016d1f427c5fb867c561acf9610fc94f019c348b"
,
"category"
:
"historical_design"
},
{
"from"
:
"docs/qa/2026-10-20-data-warehouse-and-pac-positioning.md"
,
"to"
:
"docs/_archive/2026-10-operations-and-design/2026-10-20-data-warehouse-and-pac-positioning.md"
,
"original_sha256"
:
"c3acd20f181483a052d66306bc007290440c6e9769bced5c62c3abfa115895fa"
,
"archived_sha256"
:
"597afabd5ecfc0a7b87babf8a428cb408101d9a051e0ef546145bcc62991cbce"
,
"category"
:
"architecture_discussion"
}
],
"changes"
:
"Add historical notices, repair repository links, label unavailable local references; preserve original content in private backup."
}
docs/_archive/README.md
View file @
15d08d44
...
...
@@ -4,6 +4,13 @@
归档保存历史设计、实验、排查与交付证据。文件名与原文保留;移动时修正引用。归档不等于问题关闭,不以历史环境快照判断当前运行状态。
## 2026-10-22 整理
-
[
美亚与患者事实开发历史
](
2026-10-meyer-and-patient-facts/README.md
)
:8 份实现、字段审核与验证记录。
-
[
运维验收与架构讨论
](
2026-10-operations-and-design/README.md
)
:11 份 Push、日期、投诉验收记录及数仓、影像方案讨论,含不同版本 HTML。
内部联调原文与一次性检查产物另行备份,不作为正式产品文档发布。
## 2026-10-14 整理
| 分类 | 入口 | 移入文件 | 原位置与归档依据 |
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment