资讯动态

照料护理服务实施方案PDF抽取、结算引擎与入户核查落地

发布时间:2026/9/17 13:36:33 来源:尧图企业网站定制
简介《青白江区为分散供养城乡特困人员购买照料护理服务实施方案2020年修订》PDF文档面向民政系统经办人员、镇街道工作人员、承接政府购买服务的社会组织与社工机构以及关注特困人员救助供养政策的研究者。文件明确照料护理服务的购买主体、受托方范围、必须购买的每周探视与陪同看病、住院陪护等内容并按生活自理能力分档给出不低于当地低保标准0.2、0.3、0.4倍的购买标准同时说明经费专款专用、资金拨付结算、协议签订与考核测评要求。文末附服务协议参考模板、服务人员名单及金额表、工作台账与明细表、测评考核评估表等附件可直接作为基层执行与培训的文本参照。压缩包共1个PDF文件约397KB单文件结构便于检索引用。已有394人学习下载。1. 拿到照料护理服务实施方案 PDF先拆规则再谈排版很多人拿到这类 PDF 的第一反应是转 Word 或者照着附件重画表格但这份 2020 年修订的照料护理服务实施方案本质上是一份写得很松的需求说明附件 1 是协议模板附件 2 是人员名单与服务金额附件 3 是工作台账加服务明细表附件 4 是测评考核表正文里还埋着结算口径和约束条款。真正要落地的只有三件事——服务项目怎么编码、钱怎么算、服务真不真怎么验。下面按这三条线走先给数据模型和 PDF 抽取再给结算引擎最后落到照片查重、抽样核查和回归用例。适合做养老、社工服务、购买服务类业务系统的后端同学以及要把纸质台账电子化的交付人员。2. PDF 字段抽取与服务项目字典把附件 2/3/4 落成三张表这份方案的信息密度其实不低但它把所有结构化信息塞进了四个附件和若干条款里。工程上第一步不是建系统而是判断哪些字段能被机器稳定抽出来、哪些必须留人工环节。抽取的边界一旦划错后面结算再准也是错的——因为脏数据已经进了库。2.1 台账行的正则抽取extract_text 比 extract_tables 更稳附件 3 的工作台账是最典型的坑表格列窄、日期分两列月、日、签字栏是手写图片。用extract_tables()处理这类表合并单元格一多就会错列反而按文本行做正则更可靠。import pdfplumber import re # 台账每行大致形如6 12 张某 李某 30.00 13800000000 探视 LEDGER_ROW re.compile( r^(?Pmonth\d{1,2})\s(?Pday\d{1,2})\s r(?Pperson[\u4e00-\u9fa5]{2,6})\s r(?Pworker[\u4e00-\u9fa5]{2,6})\s r(?Pamount\d(?:\.\d{1,2})?)\s r(?Pphone1\d{10})\b ) def parse_ledger(pdf_path: str) - list[dict]: rows [] with pdfplumber.open(pdf_path) as pdf: for pno, page in enumerate(pdf.pages, start1): # x_tolerance 默认 3列窄时会并列调到 1.5 更贴近原始列宽 text page.extract_text(x_tolerance1.5) or for line in text.splitlines(): m LEDGER_ROW.match(line.strip()) if m: rows.append({**m.groupdict(), page: pno}) return rows这段逻辑的关键在两点。其一x_tolerance决定同一行内相邻字符是否被合并成一个文本块台账的服务事项列和金额列挨得很近默认值容易把两列粘在一起抽出来就是脏行调到 1.5 后再配合正则里的\s分隔命中率会明显上升。其二签名和捺印是图片对象extract_text只能拿到空白所以系统里要有一个sign_status字段取值是「已核对 / 缺失 / 存疑」由人工把扫描件对照完再回填不要指望 OCR 自动判定手写签名。不同附件的抽取方式差别很大先分清楚再动手附件内容落库目标表抽取方式注意附件 1协议参考模板不入库存对象存储仅抽条款口径结算规则从条款里抠不当正文附件 2人员名单及服务金额sp_person/sp_settlement_monthextract_tables()二维表末尾合计行必须过滤否则人数翻倍附件 3工作台账 服务明细表sp_service_log正则按行抽取签字栏为手写留人工核对附件 4测评考核评估表sp_satisfaction人工录入或扫描件兜底一行一个服务人员不是一行一个对象提示附件 2 的名单表带公章和「填报人 / 审核人 / 分管领导」三行页脚extract_tables()会把这些当成数据行务必在入库前按行数阈值和关键词过滤。2.2 三张核心表服务对象、项目字典、服务流水不管前端长什么样后端只需要三张主表加一张配置表。服务对象表的难点在身份证号——它既是业务主键也是敏感信息落库只存哈希姓名和住址按需脱敏展示。CREATE TABLE sp_person ( person_id TEXT PRIMARY KEY, -- 业务主键建议取 身份证号区划码 的 SHA256 前 32 位 person_name TEXT NOT NULL, id_card_hash TEXT NOT NULL UNIQUE, -- 不落明文 town_code TEXT NOT NULL, -- 镇街道 village_code TEXT, -- 村社区 residence_type SMALLINT NOT NULL, -- 1 城市 2 农村 ability_level SMALLINT NOT NULL, -- 1 自理 2 部分失能 3 完全失能 in_psych_hosp BOOLEAN NOT NULL DEFAULT FALSE, -- 长期入住精神病院排除项 status SMALLINT NOT NULL DEFAULT 1, -- 1 在册 2 减员 exit_date DATE, -- 减员日期死亡、散转集、迁出 created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE sp_service_log ( log_id BIGSERIAL PRIMARY KEY, person_id TEXT NOT NULL REFERENCES sp_person(person_id), item_code TEXT NOT NULL, service_date DATE NOT NULL, worker_id TEXT NOT NULL, amount NUMERIC(10,2) NOT NULL, photo_path TEXT, photo_fp TEXT, -- 图片内容指纹用于查重 sign_status SMALLINT NOT NULL DEFAULT 0, billable BOOLEAN NOT NULL DEFAULT TRUE ); -- 同一天同一项目的可计费记录只允许一条把去重规则顶到数据库层 CREATE UNIQUE INDEX uk_log_billable ON sp_service_log (person_id, service_date, item_code) WHERE billable TRUE;ability_level用枚举而不是直接存系数是因为系数会随政策调整in_psych_hosp单独建字段而不是靠备注文本判断是因为它直接决定这个人要不要进结算范围。那个部分唯一索引值得说一句方案里的规则是「一个服务对象一天不能享受两次及以上相同的服务」但它约束的是计费不是记录——服务人员真的上门两次第二次也应该能录进来只是billable置为 FALSE。用部分索引正好表达这个语义PostgreSQL 支持MySQL 需要改成生成列加普通唯一索引。2.3 必做/选做与最低次数把正文和附件的口径冲突做成配置这份材料里有一处明显的口径不一致正文写「必须购买探视服务每周至少一次」而附件 3 明细表的备注写「每周上门探视 2 次每月不少于 8 次」。工程上不要二选一写死在代码里而是做成带生效月份的参数表默认取从严值。# sp_rule_config 的默认种子数据key effective_month 联合主键改规则只新增不覆盖 DEFAULT_RULES { visit_min_per_week: 2, # 正文为 1附件为 2从严取 2 visit_min_per_month: 8, # 缺省按 4 周折算与周口径自洽 cap_coef: {1: 0.2, 2: 0.3, 3: 0.4}, verify_ratio: 0.30, # 结算前入户核查比例下限 satisfy_threshold: 0.85, # 满意度处置阈值 psych_hospital_excluded: True, }服务项目字典同样要落表而不是写死在前端下拉框里。必做项目就三类探视、助医陪同看病、挂号、取药、协助吃药、住院陪护选做项目由各镇街道自定。字典表里给必做项打item_type 1并挂上min_times_per_month结算时用它做达标校验而不是做扣款依据——材料里对不达标的表述是「可以取消资格」属于处置手段不是金额扣减规则两者在代码里必须分开。3. 结算引擎0.2/0.3/0.4 倍标准、按月封顶与同日去重结算这块是整个系统的核心也是最容易出账实不符的地方。规则本身不复杂复杂的是边界跨月的减员、超标后的封顶、同一天的重复服务、必做项缺失但不影响金额的情形都得有明确落点。3.1 应享标准与封顶口径应享标准按评估后的自理能力等级取当地城乡居民月低保标准的 0.2、0.3、0.4 倍并且「当月服务费超过享受标准时仍按享受标准结算」也就是封顶。金额一律用Decimal不要用 float。from decimal import Decimal, ROUND_HALF_UP COEF {1: Decimal(0.2), 2: Decimal(0.3), 3: Decimal(0.4)} def monthly_cap(ability_level: str, low_income_month: Decimal) - Decimal: 应享标准 当地城乡居民月低保标准 × 系数四舍五入到分 return (low_income_month * COEF[ability_level]).quantize( Decimal(0.01), roundingROUND_HALF_UP)两个容易踩的坑一是低保标准要按「当月有效版本」取历史月份重算必须用历史值用当前值倒推会让去年的账全对不上二是城乡口径要分开城市和农村的低保标准经常不同residence_type字段就是为这个准备的别图省事共用一张标准表。3.2 同日去重、减员清零与结算主流程主流程按「先过滤、再去重、后封顶」的顺序顺序错了结果就错。减员对象在减员日期之后的服务不再结算当月未使用完的额度清零不做结转。from datetime import date from decimal import Decimal def settle_person(person: dict, logs: list[dict], low_income_month: Decimal, cfg: dict) - dict: cap monthly_cap(person[ability_level], low_income_month) kept, dropped, seen [], [], set() # 按服务日期排序保证同一天同项目先到先算结果可复现 for lg in sorted(logs, keylambda x: (x[service_date], x[item_code])): if person.get(exit_date) and lg[service_date] person[exit_date]: dropped.append({**lg, reason: EXIT_AFTER}); # 减员后不再计费 continue key (lg[service_date], lg[item_code]) if key in seen: dropped.append({**lg, reason: DUP_SAME_DAY}); # 一天同一项目只算一次 continue seen.add(key) kept.append(lg) used sum((l[amount] for l in kept), Decimal(0.00)) payable min(used, cap) # 按月封顶 visit_cnt sum(1 for l in kept if l[item_code] VISIT) flags [] if visit_cnt cfg[visit_min_per_month]: flags.append(MISS_REQUIRED_VISIT) if used cap: flags.append(OVER_CAP) return {cap: cap, used: used, payable: payable, flags: flags, dropped: dropped, kept: kept}used取的是保留明细的金额之和而不是拿去重后的次数乘以单价——因为选做项目单价不统一重算单价会引入第二套口径。payable用min()实现封顶注意封顶后不减次数明细仍然全部保留在库里差额记为「政策性封顶」方便对账时解释为什么服务总额大于结算总额。各种异常汇总成一张码表前端和报表都按这个码展示异常码触发条件处理动作DUP_SAME_DAY同一对象同一天同一项目多条记录只结算一条其余标记不计费EXIT_AFTER服务日期晚于减员日期不结算当月剩余额度清零OVER_CAP明细合计超过应享标准按标准封顶结算差额单列MISS_REQUIRED_VISIT当月探视次数低于下限挂预警转人工复核不自动扣款PHOTO_MISSING/PHOTO_REUSE缺照片或照片指纹重复该条明细不计费退回补录SIGN_MISSING无服务对象签字确认不计入当月结算汇总3.3 重算幂等结算单以对象加月份唯一月度结算一定会被要求重跑可能是台账补录也可能是规则调整。结算单表以(person_id, month_key)建唯一键重算走 upsert并且每次都写一条sp_settle_run记录run_id、输入明细集合的哈希、使用的规则版本号。这样当「结算金额对不上」时能直接定位是数据变了还是规则变了。我一般还会在结算单上留input_hash字段下次重算时如果哈希没变就直接跳过计算省时间也避免无意义的版本膨胀。4. 台账明细与入户核查照片指纹、30% 抽样与满意度计算钱算对了只完成一半另一半是证明服务真实发生。方案里给了三道口子一次服务一张照片、结算前按不低于 30% 的比例入户核查、满意度低于 85% 可终止合作。这三条在系统里分别对应图片指纹校验、确定性抽样和加权评分。4.1 一次服务一张照片命名规范加指纹查重文件命名规范能省掉大量人工核对但真正能拦住问题的是内容指纹——最常见的上报手法就是一张照片填好几天。import hashlib, pathlib, re NAME_RE re.compile( r^(?Pperson[0-9a-f]{8})_(?Pdate\d{8})_(?Pitem[A-Z])_(?Pseq\d{1,2})\.(jpg|png)$) def fingerprint(p: str) - str: return hashlib.md5(pathlib.Path(p).read_bytes()).hexdigest() def audit_photos(records: list[dict]) - list[dict]: seen {} for r in records: fp fingerprint(r[photo_path]) r[photo_fp] fp if fp in seen and seen[fp][person_id] ! r[person_id]: r[flag] PHOTO_REUSE # 同一张图用在别的对象身上直接不计费 elif fp in seen and seen[fp][service_date] ! r[service_date]: r[flag] PHOTO_REUSE # 同一对象不同日期复用同一张图 seen.setdefault(fp, r) return records指纹比对要在入库时跑一遍、结算前再跑一遍入库时拦截能减少后续返工结算前再跑是因为台账经常是批量补录的补录脚本绕过前端校验的情况很常见。照片的拍摄时间可以叠加校验用 EXIF 的DateTimeOriginal和service_date比对允许前后 1 天的浮动超出就挂PHOTO_TIME_MISMATCH让人工看一眼。photo_path里的person_id用哈希前缀而不是明文姓名也顺带解决了脱敏要求。4.2 30% 入户核查的确定性抽样「结算前要对受托方提交的台账及服务明细表进行核对并入户核查不低于 30% 的对象」——这个比例不能随机抽一次就完事因为同一批数据每次抽出来的人不一样人工跑两趟成本翻倍。做法是用哈希做伪随机保证同一批次可复现同时按镇街道分层。WITH base AS ( SELECT person_id, town_code, payable, ROW_NUMBER() OVER (PARTITION BY town_code ORDER BY md5(person_id || month_key || v2020)) AS rn, COUNT(*) OVER (PARTITION BY town_code) AS cnt FROM sp_settlement_month WHERE month_key 2020-06 AND payable 0 ) SELECT person_id, town_code, payable FROM base WHERE rn GREATEST(1, CEIL(cnt * 0.30)) ORDER BY town_code, rn;md5(person_id || month_key || 固定盐)代替random()同一个月份无论跑多少次抽出来的人完全一致核查员拿到的名单不会变PARTITION BY town_code保证每个镇街道都有样本不会出现某个镇一个人都没抽到的极端情况GREATEST(1, ...)兜住人数少于 4 人的小单位cnt * 0.30向上取整本身就满足「不低于 30%」。抽样结果要落一张sp_verify_task表记录核查人、核查日期、结论和照片核查不通过的明细回写billable FALSE再触发重算。4.3 满意度 85% 阈值的加权计算附件 4 的测评表用的是三档满意、基本满意、不满意。如果直接按「满意数 / 总数」算口径偏松基本满意会被当成不满意实际上业务上是要区分权重的。WEIGHT {满意: 1.0, 基本满意: 0.7, 不满意: 0.0} def satisfaction(scores: list[str], threshold: float 0.85) - dict: if not scores: return {rate: None, pass: None} # 无样本不判定避免除零 rate sum(WEIGHT[s] for s in scores) / len(scores) return {rate: round(rate, 4), pass: rate threshold}基本满意的权重 0.7 是经验值必须和业务方书面确认后写进配置表别固化在代码里。另外要注意方案里的表述是满意度低于 85% 或考核不合格「可以取消其服务资格」属于处置选项而非自动动作所以系统输出的是一个复核工单由人工决策不是把结算直接判死。考核周期是「一月一考核」那么满意度的统计窗口也要跟着按月切片而不是取历史累计值。5. 用历史台账做回归把结算口径钉死在测试用例里规则引擎最怕的不是写错而是改错之后没人发现。这类项目的上线节奏通常是「先跑一个月历史数据对账再切换新月份」所以回归用例的写法比代码本身更重要。我一般会挑一个数据最乱的月份——有重复上报、有月中减员、有超标封顶的——把它固化成一组用例。import pytest from datetime import date from decimal import Decimal from settle import settle_person CFG {visit_min_per_month: 8} def test_dup_same_day_only_billed_once(): person {ability_level: 2, exit_date: None} logs [ {service_date: date(2020, 6, 3), item_code: VISIT, amount: Decimal(20.00)}, {service_date: date(2020, 6, 3), item_code: VISIT, amount: Decimal(20.00)}, ] r settle_person(person, logs, Decimal(800.00), CFG) assert r[used] Decimal(20.00) # 只算一次 assert r[cap] Decimal(240.00) # 800 × 0.3 assert r[payable] Decimal(20.00) assert MISS_REQUIRED_VISIT in r[flags] # 探视只有 1 次未达 8 次下限 def test_exit_date_clears_rest_of_month(): person {ability_level: 3, exit_date: date(2020, 6, 15)} logs [ {service_date: date(2020, 6, 10), item_code: VISIT, amount: Decimal(30.00)}, {service_date: date(2020, 6, 20), item_code: NURSE, amount: Decimal(200.00)}, ] r settle_person(person, logs, Decimal(800.00), CFG) assert r[payable] Decimal(30.00) # 减员后的住院陪护不计费 assert any(d[reason] EXIT_AFTER for d in r[dropped])这两个用例覆盖了最容易出争议的两条规则同日去重和减员清零。断言里要写具体金额而不是「大于 0」否则口径悄悄变了测试照样绿。历史数据对账时还有个技巧——把系统算出来的payable和原台账里手工汇总的「实际服务金额」逐条比对差异行按异常码分组统计如果某类异常码占比突然变高基本就是抽取环节出了问题而不是规则问题。5.1 留痕字段与配置版本明细表上要有created_by、created_at、source_doc_hash源 PDF 的哈希、photo_fp。修改一律走追加表sp_log_amend不在原行做 update因为「账实不符」的追溯靠的就是这条链。规则配置表sp_rule_config用key effective_month联合主键改规则只新增行不覆盖历史月份重算时按当月生效版本读取。把visit_min_per_month、verify_ratio、satisfy_threshold这三个值固定在配置里并带生效月份是这类系统能扛住口径调整的最小代价。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价