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
fa9ba3be
Commit
fa9ba3be
authored
Oct 15, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(sync): preserve UTC timestamps in Friday cold exports
parent
fa09e5ff
Pipeline
#3815
failed in 0 seconds
Changes
2
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
79 additions
and
9 deletions
+79
-9
apps/pac-service/data/friday/export.sh
+7
-9
apps/pac-service/tests/friday-export-timezone.spec.ts
+72
-0
No files found.
apps/pac-service/data/friday/export.sh
View file @
fa9ba3be
...
...
@@ -62,8 +62,8 @@ if [ -n "$CLINICS$SINCE" ]; then
let ids = null;
if (cl.length) ids = db.med_emr_info.distinct("patientId", Object.assign({ organizationId: { $in: cl.flatMap(both) } }, base)).map(S);
if (sn) {
//
clinicTime 是"北京墙钟伪装成 UTC"的 Date(见下方 EMR 导出注释)→ cutoff 按 Z 构造即对齐存储
const set = new Set(db.med_emr_info.distinct("patientId", Object.assign({ clinicTime: { $gte: new Date(sn + "T00:00:00
Z
") } }, base)).map(S));
//
--since 按宿主北京时间零点筛选;Mongo Date 保存真实 UTC 瞬间。
const set = new Set(db.med_emr_info.distinct("patientId", Object.assign({ clinicTime: { $gte: new Date(sn + "T00:00:00
+08:00
") } }, base)).map(S));
ids = ids === null ? Array.from(set) : ids.filter((x) => set.has(x));
}
print(JSON.stringify(ids ?? []));
...
...
@@ -171,14 +171,12 @@ mysql_csv "SELECT id,tenant_id,organization_id,customer_id,task_director_id,task
echo
"── Mongo 导出(med_emr_info,status∈{3,4} 正式病历)──"
# 不用 mongoexport:Extended JSON 会把 Date/Long 包成 {$date}/{$numberLong} 对象,破坏平铺契约。
# mongosh 手动平铺:Date→
naive 北京墙钟
字符串,Long→字符串,数组(diag/treat/dispose/examine)原样保留
# mongosh 手动平铺:Date→
带 Z 的 ISO 8601
字符串,Long→字符串,数组(diag/treat/dispose/examine)原样保留
# (PAC split_json_array 原生支持已 parse 数组)。产出 JSON 数组文件(cold-import json 格式)。
#
# ⚠️ Date 时区实证(2026-07-20,clinicTime/createdGmtAt 小时分布双证):FRIDAY 写库时把
# 北京墙钟"伪装成 UTC"存进 Mongo Date(直读 UTC 字段=营业时间双峰 上午7-10/下午13-17;
# 按真 UTC 解释则一半病历落深夜——荒谬)。故导出**不能** toISOString 带 Z(会被 PAC 当真
# UTC 再 +8,EMR 链时间全偏 8 小时,已踩):把 UTC 字段值直读为墙钟,输出 naive
# "YYYY-MM-DD HH:mm:ss",交给 manifest timezone=Asia/Shanghai 解释 → 瞬间正确。
# Mongo Date 保存真实 UTC 瞬间(2026-10-15 按同一预约的 MySQL 到诊时间与 Mongo clinicTime 核实)。
# 保留 toISOString() 的 Z 和毫秒;截掉 Z 会被 PAC 按 Asia/Shanghai 再转换,导致时间提前 8 小时。
# MySQL 无时区 datetime 仍按宿主北京时间导出,由 PAC 统一归一。
# 诊断字典 map(diag_code→std_code)—— diag[] inline std_code 的 within-host join;linkCode 在 Mongo、
# 字典在 MySQL,故预取成 map 挂进 mongo 容器,eval 里逐元素解析(不再 PAC 侧 lookup std_diag)。
printf
'SELECT diag_code,std_code FROM emr.std_diag\n'
| docker run
-i
--rm
mysql:8 mysql
\
...
...
@@ -196,7 +194,7 @@ echo " std_diag_map.json: $(node -e 'process.stdout.write(String(Object.keys(JS
MONGO_ARGS
=(
-v
"
$PWD
/std_diag_map.json:/std_diag_map.json:ro"
)
if
[
-n
"
$COHORT_IN
"
]
;
then
MONGO_ARGS+
=(
-v
"
$PWD
/cohort_ids.json:/cohort_ids.json:ro"
-e
"COHORT=1"
)
;
fi
docker run
--rm
${
MONGO_ARGS
[@]+
"
${
MONGO_ARGS
[@]
}
"
}
mongo:7 mongosh
"
$FRIDAY_MONGO_URI
"
--quiet
--eval
'
const flat = (v) => v instanceof Date ? v.toISOString()
.slice(0, 19).replace("T", " ")
const flat = (v) => v instanceof Date ? v.toISOString()
: (v && v.constructor && v.constructor.name === "Long") ? String(v)
: v === undefined ? null : v;
// id 类字段字符串化:Mongo 存的是 number/Long 混型,canonical 契约要 string
...
...
apps/pac-service/tests/friday-export-timezone.spec.ts
0 → 100644
View file @
fa9ba3be
import
{
readFileSync
}
from
'node:fs'
;
import
{
join
}
from
'node:path'
;
import
{
runInNewContext
}
from
'node:vm'
;
import
{
normalizeDatetime
}
from
'../src/modules/sync/assembler/field-mapper'
;
// 执行脚本内实际交给 mongosh 的代码,无需连接数据库或运行完整导出。
const
script
=
readFileSync
(
join
(
__dirname
,
'../data/friday/export.sh'
),
'utf8'
);
const
programs
=
Array
.
from
(
script
.
matchAll
(
/--eval '
([\s\S]
*
?)
'/g
),
(
m
)
=>
m
[
1
]
!
);
if
(
programs
.
length
!==
2
)
throw
new
Error
(
'未找到 cohort 与病历导出的 mongosh 程序'
);
const
[
cohortProgram
,
exportProgram
]
=
programs
as
[
string
,
string
];
describe
(
'FRIDAY Mongo 导出时间'
,
()
=>
{
it
.
each
([
'2026-10-15T06:40:00.000Z'
,
// 北京 14:40
'2026-10-14T16:00:00.000Z'
,
// 北京次日零点
'2026-10-15T13:36:24.634Z'
,
// 保留源时间毫秒
])(
'导出后经 Asia/Shanghai 归一仍是原始时刻: %s'
,
(
instant
)
=>
{
const
output
:
string
[]
=
[];
runInNewContext
(
exportProgram
,
{
Date
,
print
:
(
line
:
string
)
=>
output
.
push
(
line
),
require
:
()
=>
({
readFileSync
:
(
path
:
string
)
=>
{
if
(
path
===
'/std_diag_map.json'
)
return
'{}'
;
throw
new
Error
(
'全量导出无 cohort 文件'
);
},
}),
db
:
{
med_emr_info
:
{
find
:
()
=>
({
forEach
:
(
visit
:
(
row
:
Record
<
string
,
unknown
>
)
=>
void
)
=>
visit
({
emrSubId
:
'emr-1'
,
patientId
:
123
,
status
:
3
,
clinicTime
:
new
Date
(
instant
),
createdGmtAt
:
new
Date
(
instant
),
updatedGmtAt
:
new
Date
(
instant
),
}),
}),
},
},
});
const
[
row
]
=
JSON
.
parse
(
output
.
join
(
'
\
n'
))
as
Array
<
Record
<
string
,
unknown
>>
;
for
(
const
field
of
[
'clinic_time'
,
'created_gmt_at'
,
'updated_gmt_at'
])
{
expect
(
row
!
[
field
]).
toBe
(
instant
);
expect
(
new
Date
(
normalizeDatetime
(
String
(
row
!
[
field
]),
'Asia/Shanghai'
)).
toISOString
())
.
toBe
(
instant
);
}
});
it
(
'按北京时间 --since 零点选人,包含当天前 8 小时并排除前一天'
,
()
=>
{
const
rows
=
[
{
patientId
:
'before'
,
status
:
3
,
clinicTime
:
new
Date
(
'2026-10-14T15:59:59.999Z'
)
},
{
patientId
:
'midnight'
,
status
:
3
,
clinicTime
:
new
Date
(
'2026-10-14T16:00:00Z'
)
},
{
patientId
:
'early'
,
status
:
4
,
clinicTime
:
new
Date
(
'2026-10-14T23:59:59Z'
)
},
{
patientId
:
'later'
,
status
:
3
,
clinicTime
:
new
Date
(
'2026-10-15T00:00:00Z'
)
},
{
patientId
:
'draft'
,
status
:
2
,
clinicTime
:
new
Date
(
'2026-10-15T00:00:00Z'
)
},
];
let
result
:
string
[]
=
[];
runInNewContext
(
cohortProgram
,
{
Date
,
process
:
{
env
:
{
SN
:
'2026-10-15'
}
},
print
:
(
line
:
string
)
=>
{
result
=
JSON
.
parse
(
line
)
as
string
[];
},
db
:
{
med_emr_info
:
{
distinct
:
(
_field
:
string
,
query
:
{
status
:
{
$in
:
number
[]
};
clinicTime
:
{
$gte
:
Date
}
})
=>
rows
.
filter
((
r
)
=>
query
.
status
.
$in
.
includes
(
r
.
status
)
&&
r
.
clinicTime
>=
query
.
clinicTime
.
$gte
).
map
((
r
)
=>
r
.
patientId
),
},
},
});
expect
(
result
).
toEqual
([
'midnight'
,
'early'
,
'later'
]);
});
});
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