
【免费下载链接】freehirefreehire — the open-source search engine for job seekers项目地址https://gitcode.com/GitHub_Trending/fr/freehire点击查看免费下载本文以 add-german-it-classify-aliases 设计文档 为主线拆解 freehire 如何在确定性的标题→职业类别jobs.category词典中补齐德语 IT 职位标题的「融合复合词」fused compound别名。读完后你将掌握词边界匹配为何天生看不见Systemadministrator这类德语拼写、这一缺口如何经由search.CategoryUnresolved与is_tech三态派生把约 400 条真实职位悄悄排除出搜索索引以及「裸别名 vs 限定别名 vs 排序」三种设计取舍的判定标准与验证方法。一、问题背景当词典对德语复合词完全无词可说分类器的行为在 internal/dict/classify/dictionaries.go 与 internal/dict/classify/classify.go 中被明确约束它是一份人工策展词典而不是模型只解析已知别名解析不出来就输出空值绝不猜测。类别解析的入口是matchCategoryfunc matchCategory(lower string) string { lower ptGenderSuffix.Replace(lower) if c : matchOrdered(lower, categoryTable); c ! categoryNone { return c } return }而matchOrdered只是按优先级顺序遍历[]aliasEntry表返回第一个整词命中的别名所对应的规范值func matchOrdered(title string, table []aliasEntry) string { for _, e : range table { if wordmatch.Contains(title, e.alias, wordmatch.UnicodeBoundary) { return e.canonical } } return }这里的「整词」由 internal/dict/wordmatch/wordmatch.go 的UnicodeBoundary定义命中区间的左右两侧都必须是字符串边界或者不是 Unicode 字母/数字的字符。func UnicodeBoundary(s string, start, end int) bool { if start 0 { if r, _ : utf8.DecodeLastRuneInString(s[:start]); isWordRune(r) { return false } } if end len(s) { if r, _ : utf8.DecodeRuneInString(s[end:]); isWordRune(r) { return false } } return true }这带来两个直接结论也正是设计文档 Context 一节的核心论断连字符和空格本身是合法的边界字符所以一条裸的复合词别名已经能覆盖该词的IT-前缀或IT前缀拼写无需另立条目。俄语разработчик/программист与既有德语entwickler簇都建立在这一特性上。如果复合词本身的拼接方式不同词边界匹配无能为力。softwareentwickler、software-entwickler、software entwickler对strings.Index来说是三个不同子串因此必须写成三条别名。德语的麻烦在于第 2 类它把标题中的角色词直接粘成一个不间断的词Systemadministrator、Fachinformatiker词典里既然没有融合形式的别名匹配器就永远看不见——这与「连字符俄语标题」「空格英文标题」被正常解析的情形形成鲜明对照。设计文档还记录了一个关键的事实来源受影响标题压倒性地来自单一来源arbeitsagentur德国联邦就业局这与该来源的性质完全吻合是对本地数据库核查后得出的结论而非推测。二、下游后果不是「少一个标签」而是「从搜索里消失」这份设计之所以值得单独成篇是因为类别字段不只是展示用的元数据。设计文档在 Goals 中强调新增别名让标题解析出category这既是CategoryUnresolved读取的对象也是is_tech派生的输入tech-classification 的「已识别技术类别 → true」规则——无需单独修改tech.go。检出逻辑在 internal/search/search/document.gofunc CategoryUnresolved(j db.Job) bool { if j.Category ! || j.Source job.EmployerSource { return false } if j.IsTech.Valid j.IsTech.Bool { return false } if roletype.Derive(j.Title) ! || classify.NamesManager(j.Title) { return false } var e enrich.Enrichment if len(j.Enrichment) 0 { _ json.Unmarshal(j.Enrichment, e) } return e.Category || e.Category other }也就是说只要category为空、is_tech未被确认为真、角色类型与管理岗标记都未命中职位就会被判定为「类别未解析」。而在 cmd/embed / 搜索索引构建路径中这类职位不会进入 Meilisearch。这就是提案文档 proposal.md 中「Why」一节所描述的现象在本地开发目录库做一次核查仅arbeitsagentur一家就有约 400 条开放职位——普通的 IT 系统管理员、技术员、测试员标题——因此原因被静默排除在搜索之外。而is_tech的派生是三态的true / false / nil见 internal/job/jobderive/jobderive.gofunc deriveIsTech(category, title string, sourceHint bool) *bool { if sourceHint || classify.IsTech(title) { t : true return t } nonTech : slices.Contains(vocab.NonTechCategories, category) || classify.IsNonTech(title) if slices.Contains(vocab.TechCategories, category) !nonTech { t : true return t } if nonTech { f : false return f } return nil }一旦类别解析为devops、network_engineering、qa、software_engineering这类vocab.TechCategories成员is_tech就会被推为trueCategoryUnresolved随即返回 false。这解释了设计文档「Rejected 方案」之外的另一个判断不必触碰只做is_tech判定的tech.go因为类别已能翻转三态。三、目标与非目标设计文档把边界划得很清楚这一节完整继承其表述。目标补齐缺失的德语融合复合词别名使受影响标题解析出category——这既是CategoryUnresolved的读取对象也是is_tech派生的输入无需单独改is_tech/tech.go。每条新别名都必须有本地数据库中真实标题作为证据见 proposal.md不得靠猜与仓库AGENTS.md中「Dictionaries: ... never guess, emit nothing for unknowns」的原则一致。非目标不把Fachinformatiker拆分到所有可能的限定方向Daten- und Prozessanalyse、Digitale Vernetzung、支持类岗位等。只有两个占主导且无歧义的赛道Systemintegration / Anwendungsentwicklung获得专属类别其余措辞一律落回裸别名的devops默认值。这是为搜索可见性牺牲一部分类别精度的取舍——同一个取舍it-title-coverage规范自己在把有歧义的通用 IT 标题「IT Specialist」「IT Technician」归入support时已经做过。不回溯重分类既有行。词典条目只影响之后的摄取ingest以及下一次backfill-derive运行跑这个 backfill 并执行随后的make reindex是部署期的运维动作见 proposal.md 的 Impact不属于本次变更的任务。不修改internal/dict/classify/tech.go独立的纯is_tech检测器——没必要因为已解析的类别通过既有三态派生规则就能把is_tech翻成 true。四、五项设计决策与取舍依据4.1 六个无歧义词使用裸别名systemadministrator、netzwerkadministrator、datenbankadministrator、netzwerktechniker、softwaretester、anwendungsentwickler六个词采用裸别名。理由是被本地库中抽样到的该词全部标题都确属 IT 工作不像「Systems Engineer」或「Systemtechniker」存在跨领域同名因此裸别名是安全的并且与文件中其他无歧义的德语/俄语裸 token 惯例一致。被否决的替代方案是为了与Systemtechniker/Systemelektroniker对称给它们统一加IT/system前缀。否决理由是——这会静默排除大量不带任何前缀的真实抽样标题例如裸的Datenbankadministrator (m/w/d)而真实数据并未显示存在这种风险。4.2Systemtechniker/Systemelektroniker只接受限定形式数据库样本中包含非 IT 用法「Systemtechniker Elektrotechnik」「Systemtechniker Sicherheitstechnik」。所以只有IT限定的写法连字符形式与空格形式各算一条独立别名才会被解析裸词保持未解析。这与it-title-coverage中「Systems Engineer」家族对非 IT 同名词的盲化处理一脉相承。4.3Fachinformatiker限定短语声明在裸回退之前遵循文件已有的「更具体的别名在前」排序规则与1С/аналитик 1С的先例相同。限定短语只在限定词紧邻该词时命中中间没有für、/in、m/w/d等打断限定词不相邻的标题会落回裸别名归类为devops样本中数量占主导的赛道。4.4SPS-Programmierer被有意排除SPS-Programmierer早已是一处有记录的、刻意的排除项工业/PLC 编程与「CNC Programmer」同理。给它加别名会与既有的、有证据支撑的意图相冲突因此本次变更不碰它。4.5 在dictionaries.go中的放置位置新块直接放在俄语裸 token 簇программист/разработчик之后、「The Russian engineering family」小节之前。理由正是那个簇自己的注释所给出的裸 token 能覆盖所有连字符/空格前缀拼写而这套推理同样适用于德语融合复合词。因此新块紧邻它最接近的教义先例而不是散落在文件更早处的英文administrator/technician/tester条目旁。同时用一小段注释记录边界匹配的理据裸 vs 限定以匹配该文件其他位置已有的文档密度。五、落地实现别名表与测试的双向印证最终落地的词典块位于 internal/dict/classify/dictionaries.go紧接俄语裸 token 簇之后// German administration/technician/tester/developer fused compounds. German // joins a titles role words into one unbroken word with no separator, so // none of these can be reached by the spaced English alias they otherwise // match — same doctrine as the разработчик bare tokens above, except a // German compound has no internal separator at all, so the alias must be // the fused form itself rather than relying on a hyphen/space boundary // inside it. A hyphen or space BEFORE the compound is still a boundary, // so each bare alias below already reaches an IT-/IT -prefixed title // without a separate entry. {systemadministrator, devops}, {netzwerkadministrator, network_engineering}, {datenbankadministrator, devops}, {netzwerktechniker, network_engineering}, {softwaretester, qa}, {anwendungsentwickler, software_engineering},随后是限定形式块注释点名了造成歧义的非 IT 同名用法并说明「连字符形式与空格形式对匹配器是两个不同字符串因此各需一条」{it systemtechniker, devops}, {it-systemtechniker, devops}, {it systemelektroniker, devops}, {it-systemelektroniker, devops},最后是Fachinformatiker家族两条限定短语成对声明在裸回退之前{fachinformatiker systemintegration, devops}, {fachinformatiker für systemintegration, devops}, {fachinformatiker anwendungsentwicklung, software_engineering}, {fachinformatiker für anwendungsentwicklung, software_engineering}, {fachinformatiker, devops},这里有一个容易忽略的细节aliasEntry的规范值必须落在受控词表内因为类别集与is_tech派生共享同一套枚举。参见 internal/dict/vocab/vocab.goCategoryValues、TechCategories、NonTechCategories与残差{other}必须精确划分全集由 internal/dict/vocab/vocab_test.go 断言。本次新增别名映射到的devops、network_engineering、qa、software_engineering都是既有类别所以没有引入新类别也就不需要动词表与生成的契约。回归覆盖写在 internal/dict/classify/classify_test.go每条用例都带一句「为什么」标题期望类别设计意图Systemadministrator (m/w/d)devops裸融合形式IT-Systemadministrator (m/w/d)devops连字符是词边界裸别名即可覆盖IT Systemadministrator (m/w/d)devops空格同样是词边界Netzwerkadministrator (m/w/d)network_engineering与network administrator对齐Datenbankadministrator (m/w/d)devops与database administrator对齐Netzwerktechniker (m/w/d)/IT-Netzwerktechniker (m/w/d)network_engineering裸 前缀两种拼写Softwaretester (m/w/d)qa裸tester看不见融合形式Anwendungsentwickler (m/w/d)/Inhouse Anwendungsentwickler (m/w/d)software_engineering加入既有entwickler簇IT Systemtechniker/IT-Systemtechniker/IT Systemelektroniker/IT-Systemelektronikerdevops仅限定形式Systemtechniker (m/w/d) Elektrotechnik无电气/控制非 ITSystemtechniker Sicherheitstechnik (m/w/d)无实体安防系统非 ITFachinformatiker (m/w/d)devops裸回退从不指非 IT 岗位Fachinformatiker Systemintegration (m/w/d)/... für Systemintegration ...devops限定短语含für变体Fachinformatiker Anwendungsentwicklung (m/w/d)/... für Anwendungsentwicklung ...software_engineering开发赛道SPS-Programmierer (m/w/d)无工业/PLC与 CNC Programmer 同理注意Systemtechniker/Systemelektroniker的「裸词返回空」用例不仅仅是不写条目那么简单由于matchOrdered是按序首个命中即返回只要表中任何位置存在一个更宽泛的别名可以命中裸词就会「漏」成某个类别。这也是为什么「盲化 / 不声明」必须与「排序」联合考虑——设计文档在 Placement 一节特意把新块放在俄语簇旁正是为了避免与文件更早的英文administrator/technician/tester条目产生优先级纠缠。六、风险与权衡设计文档列了三条风险每条都给出了缓解手段值得作为后续同类扩展的模板[风险] 某个德语标题以本设计未抽样的语义使用Fachinformatiker/Systemadministrator等词可能被错误打标。缓解每一条新裸别名在被接受为「无歧义」之前都对照了本地库中该词的全量去重标题列表而不只是 Top-N 抽样两个真正暴露歧义的词被限制为仅限定形式而不是裸别名。[风险]Fachinformatiker的裸回退会把一些 Anwendungsentwicklung 赛道职位归为devops因为限定词不与词相邻例如Fachinformatiker (m/w/d) für Anwendungsentwicklung——für打断了双词短语匹配。 接受的取舍职位仍然被解析、仍可被搜索到这才是本次变更的真实目标只是类别比理想情况更粗这一点在非目标中明确记录而不是靠枚举一堆「包含 für 的短语变体」去追。[风险] 变更前已摄取的存量开放职位携带过期的空category。不是本次变更的修复范围已作为必需的后续运维步骤记录在 proposal.md 的 Impact 中。七、迁移与运行没有 schema 变更只有一次运维补齐设计文档的 Migration Plan 只有两句话但每一句都关键没有 schema 或数据迁移。部署就是一次常规代码发布随后的cmd/backfill-derivemake reindex运维轮次记录在 proposal.md任务跟踪在本次变更之外才是触达既有行的手段。具体到命令层面仓库提供了现成的入口# 1) 重算所有由 jobderive.Derive 从原始字段纯函数推导出的列 go run ./cmd/backfill-derive # 2) 全量重建 Meilisearch 索引 make reindexmake reindex的目标定义在 Makefile实际执行go run ./cmd/reindex。backfill-derive的并发与范围由环境变量控制读取位置见 cmd/backfill-derive/main.go环境变量作用备注源自源码注释BACKFILL_CONCURRENCY每行派生的 worker 池大小默认 1设为 6 曾使生产环境退化建议维持 2–3BACKFILL_DERIVE_MAX本次最多处理的行数默认 0表示不设上限BACKFILL_DERIVE_FROM_ID从指定 id 起继续断点续跑默认 0中断后可从日志给出的续跑点继续BACKFILL_DERIVE_CLOSED_WITHIN_DAYS限制只处理最近 N 天内关闭的职位默认 0backfill-derive的语义值得强调两点它是幂等的每列都是原始字段的纯函数第二次运行不会改写任何东西并且它不会触碰public_slug那是插入时铸造一次的已发布 URL重算会让所有指向该职位的链接 404。此外它对work_mode采取保留策略但对 seniority / category / skills 等字段是覆盖式重算——这正是「传播词典变更」所需要的。一个补充视角来自 internal/dict/classify/AGENTS.md开放职位会在下一次抓取时自我修正因为job.New每次摄取都跑jobderive.Derive而is_tech是RefreshUnchangedJob的匹配键之一backfill-derive真正不可替代的价值在于那些不会再被抓取的行——首当其冲是已关闭职位它们不出现在任何列表里。文档还记录了该命令在生产上的量级约 15 小时并发需压低以及is_tech不在content_hash中、因此单纯增量推送无法覆盖这些行的事实。八、验证方式与本次变更的交付清单按 tasks.md工作被拆成五组并全部勾选完成裸别名先写失败测试管理员/技术员/测试员/开发者四类再加入六条裸别名最后跑go test ./internal/dict/classify/...确认新用例通过且无回归。限定形式的 Systemtechniker / Systemelektroniker先加四条限定拼写的失败测试再加一条回归测试断言Systemtechniker (m/w/d) Elektrotechnik与Systemtechniker Sicherheitstechnik (m/w/d)不解析出类别然后才写词典条目。Fachinformatiker 家族裸词 →devopsSystemintegration 两种写法 →devopsAnwendungsentwicklung 两种写法 →software_engineering限定短语对声明在裸回退之前。非目标回归护栏断言SPS-Programmierer (m/w/d)仍然不解析出类别。收尾gofmt -l .、go vet ./...、go test ./...全绿唯一例外是一个既有的、与本 diff 无关的失败cmd/billing-sync.TestTheStoreProviderAloneKeepsTheWorkerRunning在未修改的检出上同样失败并完成一轮针对整个 diff 的评审评审者对照真实的wordmatch/classify代码验证了边界匹配与排序主张未发现功能性问题。可复现的最小验证命令go test ./internal/dict/classify/...九、把这份设计抽象成可复用的检查清单当你要为另一种语言补同类别名时这份文档实际上给出了一套可执行的判定流程先确认该语言是否把角色词融合成一个不可分词的复合词。若是德语Systemadministrator必须写出融合形式本身若否俄语把技术名放前面并用连字符Java-разработчик则裸 token 已能覆盖所有拼写。不要为「前缀」重复造条目。连字符与空格本身是词边界裸复合词别名自动覆盖IT-/IT前缀形式——测试里那两行IT-Systemadministrator/IT Systemadministrator就是在钉住这个行为。连字符形式与空格形式是两条不同别名。it-systemtechniker与it systemtechniker对strings.Index是不同子串必须各写一条。用真实数据判定「裸」还是「仅限定」。样本中出现跨领域同名Systemtechniker Elektrotechnik、Systemtechniker Sicherheitstechnik就退到限定形式样本全为 IT 才能用裸别名。判定必须针对全量去重标题而不是 Top-N。限定短语必须排在其裸回退之前但要对「限定词不相邻」的标题接受一个更粗的类别——这是可见性与精度之间的显式取舍应写进非目标而不是靠枚举变体去追。明确写下不做什么。SPS-Programmierer这类既有排除项必须有用例守住否则下一次「顺手补全」就会把它扫进软件类别。记住词典变更不会自动触达存量行go run ./cmd/backfill-derive加make reindex才是让旧职位重新可被搜索的最后一步。围绕这几个环节设计文档给出的不是一次性的补丁说明而是一套关于「确定性词典如何处理形态学差异」的可迁移方法论匹配语义由wordmatch.UnicodeBoundary决定优先级由表顺序编码歧义由「盲化不声明」表达而所有判断都必须有真实标题作为证据。赞分享【免费下载链接】freehirefreehire — the open-source search engine for job seekers项目地址https://gitcode.com/GitHub_Trending/fr/freehire点击查看免费下载相关推荐终极CaiT模型解析cait_s24_224.fb_dist_in1k的Class-Attention创新原理终极CaiT模型解析cait_s24_224.fb_dist_in1k的Class Attention创新原理 cait_s24_224.fb_dist_infreehire 目录剪枝设计解析用词典与公司桶清洗非 IT 职位freehire 目录剪枝设计解析用词典与公司桶清洗非 IT 职位 freehire 的目标是只承载 IT 相关职位但通用 ATS 板上滚滚而来的护士、厨师freehire 程序化 SEO 合集实战实时职位数标题与职位页 See also 内链设计freehire 程序化 SEO 合集实战实时职位数标题与职位页 See also 内链设计 本文以 freehire 仓库中的《Programmatic S上一篇3dsconv终极指南5分钟学会3DS游戏格式转换的完整教程下一篇Excel/WPS 自定义函数查 A 股行情:3 步装好 STOCK_DATA JS 宏, 本地量化数据即查即用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考