PostHog 数据仓库 FactorialHRIS连接器 API 对接全解析连接、分页、版本策略与增量同步决策【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog本文是 PostHog 数据仓库warehouse_sources模块中 FactorialHRIS连接器的技术对接清单API inventory详解。文章以 api_inventory.md 为骨架结合 factorial.py、settings.py、source.py 等源码实现完整展开 Factorial API 的连接方式、游标分页、增量同步策略、17 个同步端点的清单与分区设计、版本生命周期与重钉迁移。读者读完后既能掌握该连接器的完整工作原理也能了解 PostHog 数据仓库 REST 源接入REST source的通用实现范式。文档定位与整体架构api_inventory.md是 PostHog 仓库中针对 Factorial 连接器的源本地笔记source-local notes记录的是以代码为准、经官方文档交叉核对的 API 事实清单。它位于products/warehouse_sources/backend/temporal/data_imports/sources/factorial/目录下与该目录中的factorial.py核心实现、settings.py端点与字段配置、source.py连接器注册与元数据、canonical_descriptions.py端点/字段描述、tests/测试共同构成一个完整的 REST 数据源接入单元。PostHog 数据仓库源接入采用统一骨架每个外部源通过common/rest_source抽象层RESTAPIConfig、BasePaginator、rest_api_resource接入Factorial 连接器只需声明端点目录、分页器、认证方式和源级元数据即可被编排框架调度。理解 Factorial 连接器就等于理解 PostHog 对带日期版本路径 游标分页 全量刷新类 REST API 的标准接入姿势。Connection连接层细节Host 与版本化路径Factorial 使用单一全局 Host不存在按账号区分的子域名Hosthttps://api.factorialhr.com版本以日期路径段形式携带基础 URL 为https://api.factorialhr.com/api/version支持的版本标签2025-04-01、2026-04-01、2026-07-01默认源码中这些常量定义在 factorial.pyFACTORIAL_HOST https://api.factorialhr.com API_VERSION_2025_04_01 2025-04-01 API_VERSION_2026_04_01 2026-04-01 API_VERSION_2026_07_01 2026-07-01 def base_url(api_version: str) - str: return f{FACTORIAL_HOST}/api/{api_version}一个关键设计是资源偶尔会在不同版本间移动分组但资源路径与响应信封不变。即/resources/group/resource路径结构和{meta: ..., data: [...]}信封在三个版本上完全一致新版本只是增删响应字段——这些字段差异会被 PostHog 的自动推断 schemaauto-inferred schema吸收为列的变化无需代码分支。标识符序列化Identifier serialization这是本连接器最值得注意的版本差异点2026-07-01代号 Bessel将每个资源的 id 序列化为不透明字符串而非整数因为 id 已超出安全 64 位整数范围。该变化同时影响请求参数、响应和 webhook。代码采用无版本分支的兼容策略factorial.py 注释与 source.py 一致主键仍是id列该列类型不做硬编码交给自动推断type-agnostic, auto-inferred分页转发不透明游标meta.end_cursor绝不使用原始记录 id 作为分页参数因此字符串 id 对分页逻辑无影响。钉在2025-04-01/2026-04-01的源仍然得到整数 id。版本生命周期与静默漂移陷阱Factorial 每季度发布一个版本每个版本服务一年。关键行为对已退役版本的请求不会被拒绝而是使用最旧版本 schema提供服务——这意味着过期的版本钉pin会静默漂移而不是报错失败。由此产生的工程结论api_inventory.md 原文要点2025-04-01已于 2026-04-01 过期在 source.py 中被标记为 deprecated迁移0164_repin_factorial_api_version将源级钉批量重钉到2026-07-01由于厂商是向前回退而不是报错不存在版本被拒状态需要加入get_non_retryable_errors。源码元数据印证source.pyclass FactorialSource(ResumableSource[FactorialSourceConfig, FactorialResumeConfig]): lists_tables_without_credentials True # static endpoint catalog — safe for public docs supported_versions (API_VERSION_2025_04_01, API_VERSION_2026_04_01, API_VERSION_2026_07_01) default_version API_VERSION_2026_07_01 api_docs_url https://apidoc.factorialhr.com/docs/api-versioning deprecated_versions (VersionDeprecation(versionAPI_VERSION_2025_04_01, sunset_atdate(2026, 4, 1)),)测试 test_factorial_source.py 覆盖了版本弃用语义2025-04-01的 sunset 日期为 2026-04-01而2026-04-01、2026-07-01及未钉版本均不携带弃用警告。重钉迁移0164幂等、可逆为空操作0164_repin_factorial_api_version.py 的实现要点仅更新source_typeFactorial且api_version2025-04-01的ExternalDataSource行将其重钉为2026-07-01不动 schema 级ExternalDataSchema.api_version覆盖——那是用户刻意手动钉的交给弃用警告提示用户自行迁移NULL 钉已默认解析为default_version无需更新2026-04-01仍在服务期内保持不动只匹配2025-04-01保证幂等重复执行不匹配任何行reverse 为noop重钉后的行与原生2026-07-01行无法区分回滚会误伤合法钉迁移elidableTrue且无数据/ schema 变换路径、分页游标、响应信封不变主键仍为自动推断的id列所有 schema 都是全量刷新下次调度直接重建表即可。认证方式认证x-api-key: key请求头API key 方式。Factorial 官方也支持 OAuth2但本连接器未实现——API key 认证对公司/内部集成已完全够用且授予整个账号的访问权限。源码实现细节factorial.py通过APIKeyAuth而非裸 header注册 key从而让 key 参与基于值的日志脱敏log redaction同时make_tracked_session(redact_values(api_key,))进一步脱敏。client: { base_url: base_url(api_version), auth: { type: api_key, api_key: api_key, name: x-api-key, location: header, }, paginator: FactorialCursorPaginator(), session: make_tracked_session(redact_values(api_key,)), },资源路径形态统一为/resources/group/resource例如/resources/employees/employees。端点目录见后文表格。Pagination基于记录 id 的游标分页协议侧约定游标分页基于记录 id参数limit默认与上限均为 100、after_id向前、before_id向后响应信封{meta: {...}, data: [...]}meta携带has_next_page、has_previous_page、start_cursor、end_cursor、total、limit前向翻页将after_id meta.end_cursor传入直到has_next_page为 false记录按 id 升序返回没有文档化的sort/order参数排序隐含在 id 游标遍历中。实现FactorialCursorPaginatorfactorial.py 中的FactorialCursorPaginator完整实现了上述协议核心行为init_request首次请求只注入limitPAGE_SIZEPAGE_SIZE 100无after_idupdate_state从响应 JSON 提取meta仅当has_next_page为真且存在end_cursor且本页有数据时才记录_after_id str(end_cursor)并置_has_next_page True空页兜底即使 API 误报has_next_pagetrue只要本页data为空就停止避免死循环update_request后续请求带上after_id恢复支持get_resume_state/set_resume_state以{after_id: ...}序列化游标配合ResumableSourceManager实现断点续传。测试 test_factorial.py 逐条验证了这些行为初始状态、fresh 请求只带 limit、非终止页推进 after_id、has_next_pagefalse停止、缺end_cursor停止、空页即使 API 声称还有下一页也停止、恢复状态往返。断点续传Resumablefactorial_sourcefactorial.py与ResumableSourceManager[FactorialResumeConfig]协作FactorialResumeConfig只持有一个字段after_id不透明前向游标factorial.py可恢复时将保存的after_id作为initial_paginator_state注入分页器save_checkpoint在每页产出后保存且仅当存在下一页才持久化Redis TTL 负责清理崩溃后重新抓取最后一页而非跳过合并阶段按主键去重factorial.py。端到端恢复行为测试test_factorial.pyfresh 运行每页保存游标、恢复时以保存的游标播种、所有请求都走钉定版本的路径、单页终止不保存状态、不可恢复时不加载状态。Incremental sync增量同步决策这是本连接器策略性最强的部分。核心事实Factorial服务端updated_after过滤只对少数资源有文档project_management/flexible_time_records和project_management/subprojects对更高价值的 people / time-off / attendance 流没有文档化该参数Airbyte 连接器印证了这一点除shifts外它都在客户端过滤updated_at按 warehouse-sources 接入规范一个仍然遍历每一页的客户端游标不算增量两个updated_after端点在没有真实 API key 的情况下无法用 curl 验证结论当前所有端点一律全量刷新INCREMENTAL_FIELDS {}。源码依据settings.py# Full refresh only. Factorial documents a server-side updated_after filter on only two of the # endpoints we sync — project_management/flexible_time_records and project_management/subprojects # — and not on the higher-value people/time-off/attendance streams ... INCREMENTAL_FIELDS: dict[str, list[IncrementalField]] {}schema 层随之声明supports_incrementalFalse、supports_appendFalse、incremental_fields[]source.py测试也断言所有 schema 均仅全量刷新test_factorial_source.py。升级路径一旦用带未来日期截止future-date cutoff的真实账号 curl 验证updated_after确实能收窄结果就应将flexible_time_records/subprojects提升为增量。未来方向Factorial 还提供employee_updates/*变更流资源和 webhooksapi_public/webhook_subscriptions是未来 webhook 驱动迭代的候选api_inventory.md 原文注明。Synced endpoints同步端点清单settings.py中的FACTORIAL_ENDPOINTSsettings.py定义了 17 个端点覆盖人员与组织架构、合同、休假、考勤、报销、薪酬、项目工时、招聘ATS。每个列表资源的主键都是id列分区键只在created_at可靠出现在每一行时设置事务型记录查找/配置类资源不分区。TablePathPartition keyemployees/resources/employees/employeescreated_atteams/resources/teams/teams—team_memberships/resources/teams/memberships—locations/resources/locations/locations—legal_entities/resources/companies/legal_entities—contract_versions/resources/contracts/contract_versionscreated_atleaves/resources/timeoff/leavescreated_atleave_types/resources/timeoff/leave_types—allowances/resources/timeoff/allowances—attendance_shifts/resources/attendance/shiftscreated_atexpenses/resources/expenses/expensescreated_atpayroll_supplements/resources/payroll/supplementscreated_atflexible_time_records/resources/project_management/flexible_time_recordscreated_atprojects/resources/project_management/projects—candidates/resources/ats/candidatescreated_atjob_postings/resources/ats/job_postings—applications/resources/ats/applicationscreated_at主键与分区的实现细节FactorialEndpointConfigsettings.py默认primary_keys[id]、should_sync_defaultTruepartition_key为可空字段id在2025-04-01/2026-04-01上序列化为整数在2026-07-01上为不透明字符串因此列类型交给推断见上文标识符序列化get_resourcefactorial.py统一声明data_selectordata、write_dispositionreplace、table_formatdelta——每个列表端点都把记录包在顶层data键下SourceResponsefactorial.py按分区键设置partition_modedatetime、partition_formatweek、sort_modeasc游标按 id 升序页面顺序稳定向前测试 test_factorial.py 参数化验证有分区键的端点走 datetime 周分区无分区键的端点完全跳过分区所有端点主键均为[id]。端点/列描述canonical_descriptions.pycanonical_descriptions.py 为每个端点提供文档来源的描述与关键列注释如employees的manager_id、terminated_oncontract_versions的salary_amount单位分、leaves的approved、applications的candidate_id/ats_job_posting_id等。未覆盖的列会回退到 LLM 增强因此部分覆盖是允许的。测试保证描述表的键都是真实端点避免死数据test_factorial_source.py。凭据校验Credentials validation创建源时需要对 API key 做一次真实探测factorial.py探测核心employees端点每个 HRIS 账号都有它无权限的 key 会在这里 401/403请求参数{limit: 1}携带x-api-key头超时 10 秒allow_redirectsFalse作为纵深防御即使 base URL 是硬编码的也防止 API key 被转发到重定向后的其他主机状态码映射200 → 有效401/403 → Invalid Factorial API key, or it does not have access to your accounts data.其他 → 返回实际状态码由于 Factorial API key 拥有整个账号访问权无需像 OAuth 源那样在创建时做 scope 级校验。对应测试test_factorial.py200/401/403/500 的状态码映射、探测请求精确断言URL、headers、params、allow_redirects、网络异常返回错误消息。运行时对错误的处理source.pyget_non_retryable_errors将401 Client Error、403 Client Error、Unauthorized for url映射为可操作的提示key 无效或已吊销请在 Factorial 账号设置中新建 key 并重新连接因为这些凭据问题重试无意义。Rate limits限流POST 在2025-*端点上文档化为200 req/minGET 的限流与限流响应头没有公开文档当前实现依赖 tracked session 的默认重试机制处理瞬时429/5xx。Verification status验证状态官方口径必须如实呈现端点路径、分页行为、updated_after覆盖范围均与官方文档及 Airbyte/Fivetran 连接器流清单交叉核对过未对真实账号做 curl 验证没有可用 API key连接层host、版本路径、x-api-key、错误 key 返回 401已通过未认证 curl 得到 401确认——这验证了认证通道与 401 语义但未能验证业务数据读取路径。测试覆盖总览test_factorial.py分页器状态机9 个用例、资源 shape 参数化、分区/主键参数化、端到端断点续传、凭据校验test_factorial_source.py无凭据列表明细lists_tables_without_credentialsTrue公开文档可渲染表格、schema 全量刷新断言、版本解析与透传None → 2026-07-01、版本弃用元数据。从源码结构可以推断lists_tables_without_credentialsTrue意味着get_schemas是纯静态目录无 I/O这既让公开文档可以安全渲染Supported tables章节也降低了公开页面的数据泄露风险。小结接入 Factorial 的关键决策清单版本钉是核心状态Factorial 静默前向回退而非报错必须依赖迁移 deprecated_versions元数据主动管理版本不能等失败暴露id 可能是不透明字符串主键留给推断、分页只用end_cursor即可无分支兼容2026-07-01分页要防死循环has_next_page可能误报空页兜底是必须的没有验证过的增量就不做增量客户端过滤不算增量updated_after未验证前一律全量刷新 replace写盘凭据探测要防御重定向allow_redirectsFalse防止 key 外泄续传粒度是每页每页保存游标 合并去重崩溃只重拉最后一页。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
