口径:已并入 / 子段的到访不再计入时间线与统计(导出侧一个字节不动)
真机里两行到访可能「不独立成立」:ZMERGEDTO 指向它被并入的那条、ZPARENT 说明它只是某个 父段的一部分。app 对这两种行既不显示也不计数,本站以前当成普通到访 ⇒ 时间线上出现 「同一时刻同一地点两条」、日停留重复计入(实测数据集 9 有 64 条,26 条到达时间与原件相同)。 这些行**不能删**——导出要靠它们认这次合并发生过,所以判据只立一份 stats.SoloVisitSQL, 在读的一侧收口:visitsWhere / VisitsByPlace / Bookmarked / placeCols 子查询、首次到达城市、 地图极值点、专题(详情 + 列表批量)、三处聚合的 visit_count 与 dwell_minutes, 外加 schema.sql 一段幂等回填把存量库拉正。 天气与位移**不收口**(那几小时、那段路真实发生过);导出侧一个字节不动(rond 只读 places.last_visit_at,保持全量,压根不读 visit_count / dwell_minutes)。编辑页照样列出 这些行,但打上「已并入 #562 / 子段 #374」,免得以为数据丢了。 顺带修两个自测抓出来的问题: - ingest 的地点聚合回填排在 insertEntityRaw 之前 ⇒ 判据读到空留档 ⇒ 每行都算独立到访 (整整多算一个「已并入」的量)。 - 收口后 last_visit_at 保持全量,会凑出「0 次却有最近到访」(数据集 9 有 4 个地点), 展示侧按可见到访数兜住。 验收:304 PASS / 0 FAIL / 0 SKIP(新增 3 个用例:标记后导出仍带该行、存量回填拉正、 判据只出现在读侧)。
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"rond-with-you/internal/geo"
|
||||
"rond-with-you/internal/rond"
|
||||
"rond-with-you/internal/stats"
|
||||
)
|
||||
|
||||
// ---------- 数据编辑:手动补录没带手机时的记录点,改完可导出回 rondbackup ----------
|
||||
@@ -282,8 +283,13 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) {
|
||||
vargs = append(vargs, editPerPage, (d.VisitPageNo-1)*editPerPage)
|
||||
vrows, err := s.db.QueryContext(ctx, `SELECT v.src_pk, COALESCE(p.name,''), COALESCE(p.city,''),
|
||||
COALESCE(a.name,''), COALESCE(a.id,0), v.arrival, v.departure, COALESCE(v.remark,''), COALESCE(v.emoji,''),
|
||||
v.bookmarked, v.is_user_added
|
||||
v.bookmarked, v.is_user_added,
|
||||
-- 真机里「不独立成立」的那两种行(别处已并入 / 只是某个父段的一部分):访客侧不显示它们,
|
||||
-- 这一页要看得到全部原始行,所以只打标、不过滤(口径见 stats.SoloVisitSQL)
|
||||
CASE WHEN er.raw->>'ZMERGEDTO' IS NOT NULL THEN '已并入 #'||(er.raw->>'ZMERGEDTO')
|
||||
WHEN er.raw->>'ZPARENT' IS NOT NULL THEN '子段 #'||(er.raw->>'ZPARENT') ELSE '' END
|
||||
FROM visits v LEFT JOIN places p ON p.id=v.place_id LEFT JOIN activities a ON a.id=v.activity_id
|
||||
LEFT JOIN entity_raw er ON er.dataset_id=v.dataset_id AND er.entity='ZVISIT' AND er.src_pk=v.src_pk
|
||||
WHERE `+vwhere+fmt.Sprintf(` ORDER BY v.arrival DESC LIMIT $%d OFFSET $%d`, len(vargs)-1, len(vargs)),
|
||||
vargs...)
|
||||
if err != nil {
|
||||
@@ -294,7 +300,7 @@ func (s *Server) editPage(w http.ResponseWriter, r *http.Request) {
|
||||
var e EditVisit
|
||||
var dep sql.NullTime
|
||||
if err := vrows.Scan(&e.SrcPK, &e.PlaceName, &e.City, &e.ActivityName, &e.ActivityID,
|
||||
&e.Arrival, &dep, &e.Remark, &e.Emoji, &e.Bookmarked, &e.UserAdded); err != nil {
|
||||
&e.Arrival, &dep, &e.Remark, &e.Emoji, &e.Bookmarked, &e.UserAdded, &e.Hidden); err != nil {
|
||||
vrows.Close()
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
@@ -2261,13 +2267,21 @@ func (s *Server) saveVisitTags(ctx context.Context, ex execer, datasetID int64,
|
||||
}
|
||||
|
||||
// refreshPlaceAgg 重算某地点的到访聚合(次数 / 停留 / 首末时间)。
|
||||
//
|
||||
// 次数与停留按**访客口径**算(不含已并入 / 子段,判据见 stats.SoloVisitSQL)——编辑页删掉一条
|
||||
// 普通到访后,「地点库」上那个「到访 N 次」要跟时间线对得上。
|
||||
// first_visit_at / last_visit_at 保持全量:last_visit_at 会被导出成 ZLOCATION.ZLASTARRIVALDATE_,
|
||||
// 这里改口径就等于动导出包(实测数据集 9 有 7 个地点的「最晚到达」是由被并入的行决定的)。
|
||||
func (s *Server) refreshPlaceAgg(ctx context.Context, ex execer, placeID int64) error {
|
||||
if _, err := ex.ExecContext(ctx, `UPDATE places p SET
|
||||
visit_count = COALESCE(v.c,0), dwell_minutes = COALESCE(v.d,0),
|
||||
first_visit_at = v.f, last_visit_at = v.l
|
||||
FROM (SELECT $1::bigint AS pid) x
|
||||
LEFT JOIN (SELECT place_id, count(*) c, COALESCE(sum(LEAST(COALESCE(duration_min,0), 10080)),0) d, min(arrival) f, max(arrival) l
|
||||
FROM visits WHERE place_id=$1 GROUP BY place_id) v ON v.place_id = x.pid
|
||||
LEFT JOIN (SELECT vis.place_id,
|
||||
count(*) FILTER (WHERE `+stats.SoloVisitSQL("vis")+`) c,
|
||||
COALESCE(sum(LEAST(COALESCE(vis.duration_min,0), 10080)) FILTER (WHERE `+stats.SoloVisitSQL("vis")+`),0) d,
|
||||
min(vis.arrival) f, max(vis.arrival) l
|
||||
FROM visits vis WHERE vis.place_id=$1 GROUP BY vis.place_id) v ON v.place_id = x.pid
|
||||
WHERE p.id = x.pid`, placeID); err != nil {
|
||||
return fmt.Errorf("重算地点(id=%d)聚合失败: %w", placeID, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user