add logic to handle if mark as not suit when job detail is not match

This commit is contained in:
geekgeekrun
2025-05-01 22:01:05 +08:00
parent 45d14e70fb
commit 67e4b94f9e
11 changed files with 223 additions and 60 deletions

View File

@@ -7,6 +7,7 @@
"industryList": []
},
"expectJobRegExpStr": "",
"jobNotMatchStrategy": 1,
"autoReminder": {
"throttleIntervalMinutes": 10,
"rechatLimitDay": 21,

View File

@@ -16,7 +16,7 @@ import { calculateTotalCombinations, combineFiltersWithConstraintsGenerator } fr
import { default as jobFilterConditions } from './internal-config/job-filter-conditions-20241002.json'
import { default as rawIndustryFilterExemption } from './internal-config/job-filter-industry-filter-exemption-20241002.json'
import { ChatStartupFrom } from '@geekgeekrun/sqlite-plugin/dist/entity/ChatStartupLog'
import { MarkAsNotSuitReason } from '@geekgeekrun/sqlite-plugin/dist/enums'
import { MarkAsNotSuitReason, MarkAsNotSuitOp } from '@geekgeekrun/sqlite-plugin/dist/enums'
const jobFilterConditionsMapByCode = {}
Object.values(jobFilterConditions).forEach(arr => {
@@ -79,6 +79,7 @@ const targetCompanyList = readConfigFile('target-company-list.json').filter(it =
const anyCombineRecommendJobFilter = readConfigFile('boss.json').anyCombineRecommendJobFilter
const expectJobRegExpStr = readConfigFile('boss.json').expectJobRegExpStr
const jobNotMatchStrategy = readConfigFile('boss.json').jobNotMatchStrategy ?? MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
let {
expectJobNameRegExpStr,
expectJobTypeRegExpStr,
@@ -624,20 +625,37 @@ async function toRecommendPage (hooks) {
!testIfJobTitleOrDescriptionSuit(targetJobData.jobInfo)
) {
blockJobNotSuit.add(targetJobData.jobInfo.encryptId)
try {
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_NOT_SUIT)
await hooks.jobMarkedAsNotSuit.promise(
targetJobData,
{
markFrom: ChatStartupFrom.AutoFromRecommendList,
markReason: MarkAsNotSuitReason.JOB_NOT_SUIT,
extInfo: {
bossActiveTimeDesc: targetJobData.bossInfo.activeTimeDesc,
chosenReasonInUi
if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS) {
try {
const { chosenReasonInUi } = await markJobAsNotSuitInRecommendPage(MarkAsNotSuitReason.JOB_NOT_SUIT)
await hooks.jobMarkedAsNotSuit.promise(
targetJobData,
{
markFrom: ChatStartupFrom.AutoFromRecommendList,
markReason: MarkAsNotSuitReason.JOB_NOT_SUIT,
extInfo: {
bossActiveTimeDesc: targetJobData.bossInfo.activeTimeDesc,
chosenReasonInUi
},
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
}
}
)
} catch {
)
} catch {
}
}
else if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
try {
await hooks.jobMarkedAsNotSuit.promise(
targetJobData,
{
markFrom: ChatStartupFrom.AutoFromRecommendList,
markReason: MarkAsNotSuitReason.JOB_NOT_SUIT,
extInfo: null,
markOp: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
}
)
} catch {
}
}
continue continueFind
}
@@ -773,7 +791,6 @@ export async function mainLoop (hooks) {
}
})
hooks.puppeteerLaunched?.call()
page = (await browser.pages())[0]
//set cookies
hooks.cookieWillSet?.call(bossCookies)
@@ -782,6 +799,10 @@ export async function mainLoop (hooks) {
}
await setDomainLocalStorage(browser, localStoragePageUrl, bossLocalStorage)
await page.bringToFront()
await hooks.mainFlowWillLaunch?.callAsync({
jobNotMatchStrategy,
blockJobNotSuit
})
await toRecommendPage(hooks)
// goto search

View File

@@ -51,6 +51,7 @@ const main = async () => {
pageLoaded: new SyncHook(),
cookieWillSet: new SyncHook(['cookies']),
userInfoResponse: new AsyncSeriesHook(['userInfo']),
mainFlowWillLaunch: new AsyncSeriesHook(['args']),
newChatWillStartup: new AsyncSeriesHook(['positionInfoDetail']),
newChatStartup: new AsyncSeriesHook(['positionInfoDetail', 'chatRunningContext']),
noPositionFoundForCurrentJob: new SyncHook(),

View File

@@ -1,4 +1,4 @@
import { MarkAsNotSuitReason } from "../enums";
import { MarkAsNotSuitOp, MarkAsNotSuitReason } from "../enums";
import { requireTypeorm } from "../utils/module-loader";
import { ChatStartupFrom } from "./ChatStartupLog";
const { Entity, Column, PrimaryGeneratedColumn } = requireTypeorm()
@@ -27,6 +27,11 @@ export class MarkAsNotSuitLog {
})
markReason?: MarkAsNotSuitReason
@Column({
nullable: true
})
markOp?: MarkAsNotSuitOp
@Column({
nullable: true
})

View File

@@ -3,4 +3,10 @@ export enum MarkAsNotSuitReason {
BOSS_INACTIVE = 1,
USER_MANUAL_OPERATION_WITH_UNKNOWN_REASON = 2,
JOB_NOT_SUIT = 3,
}
}
export enum MarkAsNotSuitOp {
MARK_AS_NOT_SUIT_ON_BOSS = 1,
MARK_AS_NOT_SUIT_ON_LOCAL = 2,
NO_OP = 3
}

View File

@@ -1,4 +1,4 @@
import { DataSource } from "typeorm";
import { DataSource, Raw } from "typeorm";
import { BossActiveStatusRecord } from "./entity/BossActiveStatusRecord";
import { BossInfo } from "./entity/BossInfo";
import { CompanyInfo } from "./entity/CompanyInfo";
@@ -270,7 +270,7 @@ export async function saveMarkAsNotSuitRecord(
ds: DataSource,
_jobInfo,
{ encryptUserId },
{ autoStartupChatRecordId = undefined, markFrom = undefined, extInfo = undefined, markReason = undefined } = {}
{ autoStartupChatRecordId = undefined, markFrom = undefined, extInfo = undefined, markReason = undefined, markOp = undefined } = {}
) {
const { jobInfo } = _jobInfo;
@@ -283,7 +283,8 @@ export async function saveMarkAsNotSuitRecord(
autoStartupChatRecordId,
markFrom,
markReason,
extInfo: extInfo ? JSON.stringify(extInfo) : undefined
extInfo: extInfo ? JSON.stringify(extInfo) : undefined,
markOp
}
Object.assign(markAsNotSuitLog, markAsNotSuitLogPayload)
@@ -326,3 +327,15 @@ export async function saveGptCompletionRequestRecord(
//#endregion
return
}
export async function getNotSuitMarkRecordsInLastSomeDays (ds: DataSource, days = 0) {
const repo = ds.getRepository(MarkAsNotSuitLog)
const result = await repo.findBy({
date: Raw(alias => `DATE(${alias}) >= DATE('${
new Date(
Number(new Date()) - 7 * 24 * 60 * 60 * 1000
).toISOString()
}')`)
})
return result
}

View File

@@ -22,10 +22,12 @@ import { ChatMessageRecord } from './entity/ChatMessageRecord'
import { LlmModelUsageRecord } from './entity/LlmModelUsageRecord'
import sqlite3 from 'sqlite3';
import { saveChatStartupRecord, saveJobInfoFromRecommendPage, saveMarkAsNotSuitRecord } from "./handlers";
import { saveChatStartupRecord, saveJobInfoFromRecommendPage, saveMarkAsNotSuitRecord, getNotSuitMarkRecordsInLastSomeDays } from "./handlers";
import { UpdateChatStartupLogTable1729182577167 } from "./migrations/1729182577167-UpdateChatStartupLogTable";
import minimist from 'minimist'
import { UpdateBossInfoTable1732032381304 } from "./migrations/1732032381304-UpdateBossInfoTable";
import { MarkAsNotSuitOp } from "./enums";
import { AddColumnForMarkAsNotSuitLog1746092370665 } from "./migrations/1746092370665-AddColumnForMarkAsNotSuitLog";
export function initDb(dbFilePath) {
const { DataSource } = requireTypeorm()
@@ -59,6 +61,7 @@ export function initDb(dbFilePath) {
migrations: [
UpdateChatStartupLogTable1729182577167,
UpdateBossInfoTable1732032381304,
AddColumnForMarkAsNotSuitLog1746092370665,
],
migrationsRun: true
});
@@ -97,6 +100,19 @@ export default class SqlitePlugin {
return await userInfoRepository.save(user);
}
);
hooks.mainFlowWillLaunch.tapPromise(
"SqlitePlugin",
async ({
jobNotMatchStrategy,
blockJobNotSuit
}) => {
if (jobNotMatchStrategy === MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL) {
const ds = await this.initPromise;
const last7DayMarkRecords = (await getNotSuitMarkRecordsInLastSomeDays(ds, 7) ?? []).map(it => it.encryptJobId);
last7DayMarkRecords.forEach(id => blockJobNotSuit.add(id))
}
}
);
hooks.jobDetailIsGetFromRecommendList.tapPromise("SqlitePlugin", async (_jobInfo) => {
const ds = await this.initPromise;
@@ -111,13 +127,14 @@ export default class SqlitePlugin {
});
});
hooks.jobMarkedAsNotSuit.tapPromise("SqlitePlugin", async (_jobInfo, { markFrom = ChatStartupFrom.AutoFromRecommendList, markReason = undefined, extInfo = undefined } = {}) => {
hooks.jobMarkedAsNotSuit.tapPromise("SqlitePlugin", async (_jobInfo, { markFrom = ChatStartupFrom.AutoFromRecommendList, markReason = undefined, extInfo = undefined, markOp = undefined } = {}) => {
const ds = await this.initPromise;
return await saveMarkAsNotSuitRecord(ds, _jobInfo, this.userInfo, {
autoStartupChatRecordId: this.runRecordId,
markFrom,
markReason,
extInfo
extInfo,
markOp
});
});
}

View File

@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
const viewNames = [
"v_boss_library",
"v_chat_startup_log",
"v_company_library",
"v_job_library",
"v_mark_as_not_suit_log",
];
export class AddColumnForMarkAsNotSuitLog1746092370665 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
for (const viewName of viewNames) {
await queryRunner.query(`DROP VIEW IF EXISTS "${viewName}"`);
}
if (await queryRunner.hasTable("mark_as_not_suit_log")) {
if (!await queryRunner.hasColumn("mark_as_not_suit_log", "markOp")) {
await queryRunner.addColumn(
"mark_as_not_suit_log",
new TableColumn({
name: "markOp",
type: "number",
isNullable: true,
})
);
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {}
}

View File

@@ -86,6 +86,7 @@ const runAutoChat = async () => {
pageLoaded: new SyncHook(),
cookieWillSet: new SyncHook(['cookies']),
userInfoResponse: new AsyncSeriesHook(['userInfo']),
mainFlowWillLaunch: new AsyncSeriesHook(['args']),
jobDetailIsGetFromRecommendList: new AsyncSeriesHook(['userInfo']),
newChatWillStartup: new AsyncSeriesHook(['positionInfoDetail']),
newChatStartup: new AsyncSeriesHook(['positionInfoDetail', 'chatRunningContext']),

View File

@@ -95,6 +95,9 @@ export default function initIpc() {
if (hasOwn(payload, 'expectJobDescRegExpStr')) {
bossConfig.expectJobDescRegExpStr = payload.expectJobDescRegExpStr
}
if (hasOwn(payload, 'jobNotMatchStrategy')) {
bossConfig.jobNotMatchStrategy = payload.jobNotMatchStrategy
}
if (hasOwn(payload, 'autoReminder')) {
bossConfig.autoReminder = payload.autoReminder
}

View File

@@ -71,7 +71,7 @@
</div>
<div mb42px>
<el-form-item mb0>
查看职位详情后是发起投递还是标记不合适的条件
查看职位详情后认为合适并发起投递的条件
<span font-size-12px>以下条件为空表示不筛选</span>
</el-form-item>
<div
@@ -94,38 +94,6 @@
><span><QuestionFilled w-1em h-1em mr2px /></span>如下各信息位置图示</el-button
>
</el-tooltip>
<el-tooltip
effect="light"
placement="bottom-start"
@show="gtagRenderer('tooltip_show_about_mark_not_suit_intro')"
>
<template #content>
<ol m0 line-height-1.5em w-400px pl2em>
<li>
如果查找到的职位职位名称职位类型职位描述与如下正则不匹配则这个职位将被标记为不合适
</li>
<li>
如果查找到的职位活跃时间为本月活跃或更往前的时间则这个职位将被标记为不合适
</li>
<li>
如有错误标记请在左侧<a
href="javascript:void(0)"
style="color: var(--el-color-primary)"
@click.prevent="
() => {
gtagRenderer('click_view_mansr_from_boss_b_tooltip')
$router.push('/main-layout/MarkAsNotSuitRecord')
}
"
>标记不合适</a
>记录中找到相关记录手动对这些职位发起会话
</li>
</ol>
</template>
<el-button type="text" font-size-12px
><span><QuestionFilled w-1em h-1em mr2px /></span>标记不合适机制</el-button
>
</el-tooltip>
</div>
<el-dropdown ml20px @command="handleExpectJobFilterTemplateClicked">
<el-button size="small"
@@ -187,6 +155,66 @@
</el-form-item>
</div>
</div>
<div
:style="{
display: 'grid',
gridTemplateColumns: '500px 1fr',
gap: '5px',
width: '100%',
alignItems: 'end'
}"
>
<el-form-item>
<div>
当查看职位详情后发现职位不满足如上设置的条件时
<el-tooltip
effect="light"
placement="bottom-start"
@show="gtagRenderer('tooltip_show_about_mark_not_suit_intro')"
>
<template #content>
<ol m0 line-height-1.5em w-400px pl2em>
<li>
如果查找到的职位职位名称职位类型职位描述与如上正则不匹配则这个职位将被标记为不合适
</li>
<li>
如果查找到的职位活跃时间为本月活跃或更往前的时间则这个职位将被标记为不合适
</li>
<li>
如有错误标记请在左侧<a
href="javascript:void(0)"
style="color: var(--el-color-primary)"
@click.prevent="
() => {
gtagRenderer('click_view_mansr_from_boss_b_tooltip')
$router.push('/main-layout/MarkAsNotSuitRecord')
}
"
>标记不合适</a
>记录中找到相关记录手动对这些职位发起会话
</li>
</ol>
</template>
<el-button type="text" font-size-12px
><span><QuestionFilled w-1em h-1em mr2px /></span>标记不合适机制</el-button
>
</el-tooltip>
</div>
<el-select
v-model="formContent.jobNotMatchStrategy"
@change="(value) => gtagRenderer('job_not_match_strategy_changed', { value })"
>
<el-option
v-for="op in strategyOptionWhenCurrentJobNotMatch"
:key="op.value"
:label="op.name"
:value="op.value"
>{{ op.name }}</el-option
>
</el-select>
</el-form-item>
</div>
<el-form-item
label="职位备选筛选条件当前求职期望无合适职位时自动更改Boss筛选条件查找新工作"
prop="filter"
@@ -202,7 +230,7 @@
>
</div>
</el-form-item>
<el-form-item class="last-form-item mb0">
<el-form-item class="last-form-item mb18px">
<el-button @click="handleSave">仅保存配置</el-button>
<el-button type="primary" @click="handleSubmit"> 保存配置并开始求职 </el-button>
</el-form-item>
@@ -211,7 +239,7 @@
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { ElForm, ElMessage } from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
@@ -220,6 +248,8 @@ import { calculateTotalCombinations } from '@geekgeekrun/geek-auto-start-chat-wi
import { gtagRenderer } from '@renderer/utils/gtag'
import defaultTargetCompanyListConf from '@geekgeekrun/geek-auto-start-chat-with-boss/default-config-file/target-company-list.json'
import { ArrowDown } from '@element-plus/icons-vue'
import { MarkAsNotSuitOp } from '@geekgeekrun/sqlite-plugin/src/enums'
import { debounce } from 'lodash-es'
const router = useRouter()
@@ -229,13 +259,18 @@ const formContent = ref({
anyCombineRecommendJobFilter: {},
expectJobNameRegExpStr: '',
expectJobTypeRegExpStr: '',
expectJobDescRegExpStr: ''
expectJobDescRegExpStr: '',
jobNotMatchStrategy: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
})
const currentAnyCombineRecommendJobFilterCombinationCount = computed(() => {
return calculateTotalCombinations(formContent.value.anyCombineRecommendJobFilter)
})
const unwatchAnyCombineRecommendJobFilter = ref<null | (() => void)>(null)
onBeforeUnmount(() => {
unwatchAnyCombineRecommendJobFilter.value?.()
})
electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
console.log(res)
formContent.value.dingtalkRobotAccessToken = res.config['dingtalk.json']['groupRobotAccessToken']
@@ -248,7 +283,15 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
scaleList: [],
industryList: []
}
//
unwatchAnyCombineRecommendJobFilter.value = watch(
() => formContent.value?.anyCombineRecommendJobFilter,
debounce(() => {
gtagRenderer('any_combine_filter_changed')
}, 2000),
{
deep: true
}
)
if (
res.config['boss.json']?.expectJobRegExpStr &&
typeof res.config['boss.json']?.expectJobNameRegExpStr === 'undefined' &&
@@ -262,6 +305,12 @@ electron.ipcRenderer.invoke('fetch-config-file-content').then((res) => {
formContent.value.expectJobNameRegExpStr = res.config['boss.json'].expectJobNameRegExpStr?.trim()
formContent.value.expectJobTypeRegExpStr = res.config['boss.json'].expectJobTypeRegExpStr?.trim()
formContent.value.expectJobDescRegExpStr = res.config['boss.json'].expectJobDescRegExpStr?.trim()
formContent.value.jobNotMatchStrategy = strategyOptionWhenCurrentJobNotMatch
.map((it) => it.value)
.includes(res.config['boss.json'].jobNotMatchStrategy)
? res.config['boss.json'].jobNotMatchStrategy
: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
})
const formRules = {
@@ -467,6 +516,21 @@ function handleExpectJobFilterTemplateClicked(item) {
...item.config
})
}
const strategyOptionWhenCurrentJobNotMatch = [
{
name: '在Boss直聘上标记不合适推荐这确保Boss直聘可以推荐新职位',
value: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_BOSS
},
{
name: '本地记录且1周内再次遇到这个职位时将直接跳过',
value: MarkAsNotSuitOp.MARK_AS_NOT_SUIT_ON_LOCAL
},
{
name: '本地不记录,但本次运行再次遇到这个职位时将直接跳过',
value: MarkAsNotSuitOp.NO_OP
}
]
</script>
<style scoped lang="scss">