1 line
49 KiB
JSON
1 line
49 KiB
JSON
{"repo": "bcefghj/jobos", "files": {"cli.py": {"a79886b21c5ba2268c1fed730b8c5364a9365f9d": {"t": 1785604061.614202, "n": 6309, "h": "bb41b2a035118236", "c": "\"\"\"JobOS CLI -- command-line interface for all features.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent))\n\nimport click\nfrom rich.console import Console\nfrom rich.table import Table\n\nconsole = Console()\n\n\n@click.group()\ndef cli():\n \"\"\"JobOS - AI 求职全能工具\"\"\"\n pass\n\n\n@cli.command()\n@click.option(\"--keyword\", \"-k\", default=\"AI Agent\", help=\"搜索关键词\")\n@click.option(\"--location\", \"-l\", default=\"武汉\", help=\"目标城市\")\ndef search(keyword, location):\n \"\"\"搜索岗位(使用默认平台)\"\"\"\n from crawlers.aggregator import collect_all_jobs\n jobs = collect_all_jobs(keyword, location)\n console.print(f\"[green]采集到 {len(jobs)} 个岗位[/]\")\n for j in jobs:\n console.print(f\" [{j['platform']}] {j['company']} | {j['title']} | {j.get('location','')} | {j.get('salary','')}\")\n\n\n@cli.command()\n@click.option(\"--keyword\", \"-k\", default=\"AI Agent\", help=\"搜索关键词\")\n@click.option(\"--location\", \"-l\", default=\"武汉\", help=\"目标城市\")\n@click.option(\"--platform\", \"-p\", multiple=True,\n type=click.Choice([\"boss\", \"boss_drission\", \"nowcoder\", \"liepin\", \"curated\"]),\n help=\"指定平台(可多选)\")\n@click.option(\"--pages\", default=3, help=\"每个平台爬取页数\")\ndef crawl(keyword, location, platform, pages):\n \"\"\"多平台真实爬虫采集岗位\"\"\"\n from crawlers.aggregator import collect_all_jobs\n platforms = list(platform) if platform else None\n jobs = collect_all_jobs(keyword, location, platforms=platforms, max_pages=pages)\n\n console.print(f\"\\n[bold green]采集完成: {len(jobs)} 个岗位[/]\")\n\n table = Table(title=\"采集结果\", show_lines=True)\n table.add_column(\"#\", style=\"dim\", width=4)\n table.add_column(\"公司\", style=\"bold\")\n table.add_column(\"岗位\")\n table.add_column(\"地点\")\n table.add_column(\"薪资\")\n table.add_column(\"类型\")\n table.add_column(\"来源\")\n\n for i, j in enumerate(jobs[:50], 1):\n table.add_row(\n str(i), j[\"company\"], j[\"title\"],\n j.get(\"location\", \"\"), j.get(\"salary\", \"\"),\n j.get(\"job_type\", \"\"), j[\"platform\"],\n )\n console.print(table)\n\n if len(jobs) > 50:\n console.print(f\"[dim]... 还有 {len(jobs) - 50} 个岗位,使用 export 命令导出查看全部[/]\")\n\n\n@cli.command()\n@click.option(\"--format\", \"-f\", \"fmt\",\n type=click.Choice([\"excel\", \"csv\", \"json\", \"all\"]),\n default=\"excel\", help=\"导出格式\")\n@click.option(\"--output\", \"-o\", default=None, help=\"输出文件路径\")\n@click.option(\"--all-columns\", is_flag=True, help=\"导出所有字段(默认只导出核心字段)\")\ndef export(fmt, output, all_columns):\n \"\"\"导出岗位数据为 Excel/CSV/JSON\"\"\"\n import db\n\n jobs = db.get_all_jobs_df()\n if not jobs:\n console.print(\"[yellow]数据库中没有岗位数据,请先运行 crawl 或 search 命令[/]\")\n return\n\n console.print(f\"[dim]共 {len(jobs)} 个岗位[/]\")\n\n if fmt in (\"excel\", \"all\"):\n from export.excel import export_excel\n path = export_excel(jobs, output if fmt == \"excel\" else None,\n include_all_columns=all_columns)\n console.print(f\"[green]✓[/] Excel 导出: {path}\")\n\n if fmt in (\"csv\", \"all\"):\n from export.csv_export import export_csv\n path = export_csv(jobs, output if fmt == \"csv\" else None,\n include_all_columns=all_columns)\n console.print(f\"[green]✓[/] CSV 导出: {path}\")\n\n if fmt in (\"json\", \"all\"):\n from export.json_export import export_json\n path = export_json(jobs, output if fmt == \"json\" else None)\n console.print(f\"[green]✓[/] JSON 导出: {path}\")\n\n\n@cli.command()\n@click.argument(\"jd_text\")\ndef analyze(jd_text):\n \"\"\"分析JD并评分\"\"\"\n import json\n from agents.analyst import analyze_jd, score_job\n jd_info = analyze_jd(jd_text)\n total, details = score_job(jd_info)\n console.print(f\"[green]评分: {total}[/]\")\n console.print(json.dumps(details, ensure_ascii=False, indent=2))\n\n\n@cli.command()\n@click.option(\"--keyword\", \"-k\", default=\"AI Agent\", help=\"搜索关键词\")\n@click.option(\"--location\", \"-l\", default=\"武汉\", help=\"目标城市\")\n@click.option(\"--top\", \"-n\", default=3, help=\"处理前N个岗位\")\ndef run(keyword, location, top):\n \"\"\"运行完整管线\"\"\"\n from pipeline import run_pipeline\n run_pipeline(keyword, location, top)\n\n\n@cli.command()\n@click.option(\"--keyword\", \"-k\", default=\"AI Agent\", help=\"搜索关键词\")\n@click.option(\"--location\", \"-l\", default=\"武汉\", help=\"目标城市\")\n@click.option(\"--top\", \"-n\", default=5, help=\"处理前N个岗位\")\n@click.option(\"--interval\", \"-i\", default=30, help=\"检查间隔(分钟)\")\ndef batch(keyword, location, top, interval):\n \"\"\"批量模式 -- 适合夜间自动运行\"\"\"\n from pipeline import run_pipeline\n from apscheduler.schedulers.blocking import BlockingScheduler\n\n console.print(f\"[bold cyan]批量模式启动[/]\")\n console.print(f\"关键词: {keyword} | 地点: {location} | Top-{top}\")\n console.print(f\"每 {interval} 分钟检查一次新岗位\")\n\n run_pipeline(keyword, location, top)\n\n try:\n scheduler = BlockingScheduler()\n scheduler.add_job(\n run_pipeline, \"interval\", minutes=interval,\n args=[keyword, location, top],\n )\n scheduler.start()\n except ImportError:\n console.print(\"[yellow]APScheduler未安装,使用简单循环模式[/]\")\n import time\n while True:\n time.sleep(interval * 60)\n run_pipeline(keyword, location, top)\n\n\n@cli.command()\n@click.option(\"--platform\", \"-p\", default=None, help=\"按平台过滤\")\n@click.option(\"--min-score\", \"-s\", default=None, type=float, help=\"最低评分过滤\")\ndef list_jobs(platform, min_score):\n \"\"\"列出数据库中的所有岗位\"\"\"\n import db\n jobs = db.get_jobs(limit=50, platform=platform, min_score=min_score)\n if not jobs:\n console.print(\"[yellow]数据库中没有岗位[/]\")\n return\n\n table = Table(title=f\"岗位列表 ({len(jobs)})\", show_lines=True)\n table.add_column(\"#\", style=\"dim\", width=4)\n table.add_column(\"评分\", justify=\"right\", width=6)\n table.add_column(\"公司\", style=\"bold\")\n table.add_column(\"岗位\")\n table.add_column(\"地点\")\n table.add_column(\"薪资\")\n table.add_column(\"来源\")\n\n for i, j in enumerate(jobs, 1):\n score = j.get(\"score\") or 0\n color = \"green\" if score >= 0.65 else \"yellow\" if score >= 0.45 else \"red\"\n table.add_row(\n str(i), f\"[{color}]{score:.2f}[/]\",\n j[\"company\"], j[\"title\"],\n j.get(\"location\", \"\"), j.get(\"salary\", \"\"),\n j.get(\"platform\", \"\"),\n )\n console.print(table)\n\n\nif __name__ == \"__main__\":\n cli()\n"}}, "config.yaml": {"a656aee38fa9602560de7959781ddebaff7460c8": {"t": 1785604063.8732927, "n": 911, "h": "b1d0e1c1b082050d", "c": "llm:\n provider: minimax\n model: MiniMax-M2.7\n base_url: https://api.minimaxi.com/v1\n api_key: YOUR_MINIMAX_API_KEY # 替换为你的 MiniMax API Key\n max_tokens: 4096\n temperature: 0.7\n\nsearch:\n default_location: 武汉\n default_keywords:\n - AI Agent\n - 大模型\n - LLM\n - NLP\n - 人工智能\n job_types:\n - 日常实习\n - 暑期实习\n platforms:\n - boss\n - nowcoder\n - liepin\n - zhilian\n\nscoring:\n weights:\n role_match: 0.20 # 门槛\n skill_alignment: 0.20 # 门槛\n salary: 0.10\n location: 0.10\n company_stage: 0.08\n tech_stack: 0.08\n growth_potential: 0.08\n interview_difficulty: 0.06\n timeline_match: 0.05\n work_life_balance: 0.05\n gate_threshold: 0.4\n\nresume:\n latex_engine: xelatex\n template: llm_agent\n output_dir: data/outputs\n\nscheduler:\n batch_start_hour: 22\n batch_end_hour: 6\n interval_minutes: 30\n\nnotifications:\n discord_webhook: \"\"\n wechat_webhook: \"\"\n"}}, "db.py": {"ae3672188f42bfda5c2155ded948e1848db4082c": {"t": 1785604065.2356136, "n": 6541, "h": "0770e230fdeb2757", "c": "\"\"\"SQLite storage for jobs, companies, and application tracking.\"\"\"\n\nfrom __future__ import annotations\n\nimport sqlite3\nimport json\nfrom pathlib import Path\nfrom datetime import datetime\n\nDB_PATH = Path(__file__).parent / \"data\" / \"jobs.db\"\n\n_MIGRATE_COLUMNS = [\n (\"skills\", \"TEXT\"),\n (\"degree\", \"TEXT\"),\n (\"experience\", \"TEXT\"),\n (\"company_size\", \"TEXT\"),\n (\"company_industry\", \"TEXT\"),\n (\"company_stage\", \"TEXT\"),\n (\"welfare\", \"TEXT\"),\n (\"hr_name\", \"TEXT\"),\n (\"hr_title\", \"TEXT\"),\n (\"chat_url\", \"TEXT\"),\n (\"full_jd\", \"TEXT\"),\n (\"deadline\", \"TEXT\"),\n (\"source_url\", \"TEXT\"),\n]\n\n\ndef get_conn() -> sqlite3.Connection:\n DB_PATH.parent.mkdir(parents=True, exist_ok=True)\n conn = sqlite3.connect(str(DB_PATH))\n conn.row_factory = sqlite3.Row\n return conn\n\n\ndef init_db():\n conn = get_conn()\n conn.executescript(\"\"\"\n CREATE TABLE IF NOT EXISTS jobs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n platform TEXT NOT NULL,\n job_id TEXT,\n title TEXT NOT NULL,\n company TEXT NOT NULL,\n location TEXT,\n salary TEXT,\n job_type TEXT,\n description TEXT,\n requirements TEXT,\n url TEXT,\n posted_date TEXT,\n scraped_at TEXT DEFAULT (datetime('now')),\n score REAL,\n score_details TEXT,\n status TEXT DEFAULT 'new',\n skills TEXT,\n degree TEXT,\n experience TEXT,\n company_size TEXT,\n company_industry TEXT,\n company_stage TEXT,\n welfare TEXT,\n hr_name TEXT,\n hr_title TEXT,\n chat_url TEXT,\n full_jd TEXT,\n deadline TEXT,\n source_url TEXT,\n UNIQUE(platform, job_id)\n );\n\n CREATE TABLE IF NOT EXISTS companies (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT UNIQUE NOT NULL,\n industry TEXT,\n size TEXT,\n location TEXT,\n description TEXT,\n rating REAL,\n pros TEXT,\n cons TEXT,\n avg_salary TEXT,\n work_life_balance TEXT,\n updated_at TEXT DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS applications (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id INTEGER REFERENCES jobs(id),\n resume_path TEXT,\n interview_pack_path TEXT,\n applied_at TEXT,\n status TEXT DEFAULT 'pending',\n notes TEXT,\n updated_at TEXT DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS interview_materials (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id INTEGER REFERENCES jobs(id),\n skill_tree TEXT,\n study_path TEXT,\n eight_part_essay TEXT,\n mock_questions TEXT,\n real_interview_exp TEXT,\n generated_at TEXT DEFAULT (datetime('now'))\n );\n \"\"\")\n conn.commit()\n _migrate_add_columns(conn)\n conn.close()\n\n\ndef _migrate_add_columns(conn: sqlite3.Connection):\n \"\"\"Add new columns to existing jobs table without losing data.\"\"\"\n cursor = conn.execute(\"PRAGMA table_info(jobs)\")\n existing = {row[1] for row in cursor.fetchall()}\n for col_name, col_type in _MIGRATE_COLUMNS:\n if col_name not in existing:\n conn.execute(f\"ALTER TABLE jobs ADD COLUMN {col_name} {col_type}\")\n conn.commit()\n\n\ndef insert_job(\n platform: str, title: str, company: str, *,\n job_id: str = \"\", location: str = \"\", salary: str = \"\",\n job_type: str = \"\", description: str = \"\", requirements: str = \"\",\n url: str = \"\", posted_date: str = \"\",\n skills: str = \"\", degree: str = \"\", experience: str = \"\",\n company_size: str = \"\", company_industry: str = \"\",\n company_stage: str = \"\", welfare: str = \"\",\n hr_name: str = \"\", hr_title: str = \"\", chat_url: str = \"\",\n full_jd: str = \"\", deadline: str = \"\", source_url: str = \"\",\n) -> int | None:\n conn = get_conn()\n try:\n cur = conn.execute(\n \"\"\"INSERT OR IGNORE INTO jobs\n (platform, job_id, title, company, location, salary, job_type,\n description, requirements, url, posted_date,\n skills, degree, experience, company_size, company_industry,\n company_stage, welfare, hr_name, hr_title, chat_url,\n full_jd, deadline, source_url)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,\n ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\"\"\",\n (platform, job_id or f\"{company}_{title}\", title, company, location,\n salary, job_type, description, requirements, url, posted_date,\n skills, degree, experience, company_size, company_industry,\n company_stage, welfare, hr_name, hr_title, chat_url,\n full_jd, deadline, source_url),\n )\n conn.commit()\n return cur.lastrowid\n finally:\n conn.close()\n\n\ndef update_job_score(job_db_id: int, score: float, details: dict):\n conn = get_conn()\n conn.execute(\n \"UPDATE jobs SET score=?, score_details=? WHERE id=?\",\n (score, json.dumps(details, ensure_ascii=False), job_db_id),\n )\n conn.commit()\n conn.close()\n\n\ndef get_jobs(*, min_score: float | None = None, status: str | None = None,\n platform: str | None = None, limit: int = 100) -> list[dict]:\n conn = get_conn()\n sql = \"SELECT * FROM jobs WHERE 1=1\"\n params: list = []\n if min_score is not None:\n sql += \" AND score >= ?\"\n params.append(min_score)\n if status:\n sql += \" AND status = ?\"\n params.append(status)\n if platform:\n sql += \" AND platform = ?\"\n params.append(platform)\n sql += \" ORDER BY score DESC NULLS LAST, scraped_at DESC LIMIT ?\"\n params.append(limit)\n rows = conn.execute(sql, params).fetchall()\n conn.close()\n return [dict(r) for r in rows]\n\n\ndef get_job_by_id(job_db_id: int) -> dict | None:\n conn = get_conn()\n row = conn.execute(\"SELECT * FROM jobs WHERE id=?\", (job_db_id,)).fetchone()\n conn.close()\n return dict(row) if row else None\n\n\ndef get_all_jobs_df():\n \"\"\"Return all jobs as a pandas-friendly list of dicts.\"\"\"\n conn = get_conn()\n rows = conn.execute(\"SELECT * FROM jobs ORDER BY score DESC NULLS LAST, scraped_at DESC\").fetchall()\n conn.close()\n return [dict(r) for r in rows]\n\n\ninit_db()\n"}}, "llm_client.py": {"77ac916e7a6ee61bf1e5e367151d13173410ca3d": {"t": 1785604066.5895185, "n": 2511, "h": "a4cb4225d94a2bc1", "c": "\"\"\"MiniMax M2.7 LLM client, OpenAI SDK compatible.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport yaml\nfrom pathlib import Path\nfrom openai import OpenAI\n\n_CONFIG_LOCAL = Path(__file__).parent / \"config.local.yaml\"\n_CONFIG_PATH = Path(__file__).parent / \"config.yaml\"\n\n\ndef _load_config() -> dict:\n path = _CONFIG_LOCAL if _CONFIG_LOCAL.exists() else _CONFIG_PATH\n with open(path, encoding=\"utf-8\") as f:\n return yaml.safe_load(f)\n\n\ndef get_client() -> OpenAI:\n cfg = _load_config()[\"llm\"]\n return OpenAI(api_key=cfg[\"api_key\"], base_url=cfg[\"base_url\"])\n\n\ndef chat(\n prompt: str,\n *,\n system: str = \"你是一个专业的AI求职助手。\",\n model: str | None = None,\n temperature: float | None = None,\n max_tokens: int | None = None,\n response_format: dict | None = None,\n) -> str:\n cfg = _load_config()[\"llm\"]\n client = get_client()\n\n kwargs: dict = {\n \"model\": model or cfg[\"model\"],\n \"messages\": [\n {\"role\": \"system\", \"content\": system},\n {\"role\": \"user\", \"content\": prompt},\n ],\n \"temperature\": temperature if temperature is not None else cfg[\"temperature\"],\n \"max_tokens\": max_tokens or cfg[\"max_tokens\"],\n }\n if response_format:\n kwargs[\"response_format\"] = response_format\n\n resp = client.chat.completions.create(**kwargs)\n content = resp.choices[0].message.content or \"\"\n import re\n content = re.sub(r\"<think>.*?</think>\", \"\", content, flags=re.DOTALL).strip()\n return content\n\n\ndef chat_json(prompt: str, *, system: str = \"你是一个专业的AI求职助手。回复必须是合法JSON,不要包含任何其他内容。\") -> dict:\n text = chat(prompt, system=system)\n text = _extract_json(text)\n return json.loads(text)\n\n\ndef _extract_json(text: str) -> str:\n \"\"\"Strip think tags, markdown fences, and other noise to extract pure JSON.\"\"\"\n import re\n text = re.sub(r\"<think>.*?</think>\", \"\", text, flags=re.DOTALL)\n text = text.strip()\n if \"```\" in text:\n lines = text.split(\"\\n\")\n inside = False\n json_lines = []\n for line in lines:\n stripped = line.strip()\n if stripped.startswith(\"```\"):\n inside = not inside\n continue\n if inside:\n json_lines.append(line)\n if json_lines:\n text = \"\\n\".join(json_lines)\n brace_start = text.find(\"{\")\n brace_end = text.rfind(\"}\")\n if brace_start != -1 and brace_end != -1:\n text = text[brace_start : brace_end + 1]\n return text.strip()\n"}}, "mcp_config.json": {"149af9a2b6940402eb6df8cbf614c3eb0547fe61": {"t": 1785604068.6502678, "n": 161, "h": "1f6dcf9f316f5f40", "c": "{\n \"mcpServers\": {\n \"jobos\": {\n \"command\": \"python\",\n \"args\": [\"mcp_server/server.py\"],\n \"cwd\": \"/path/to/JobOS\",\n \"env\": {}\n }\n }\n}\n"}}, "pipeline.py": {"5517c4dcd2bd97844cf27f5c88452064bb9b6846": {"t": 1785604070.0584033, "n": 11331, "h": "b6fe64d6c56e4581", "c": "\"\"\"JobOS end-to-end pipeline -- from job collection to interview prep.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nimport json\nimport time\nfrom pathlib import Path\nfrom datetime import datetime\n\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.panel import Panel\nfrom rich.progress import Progress, SpinnerColumn, TextColumn\nfrom rich import print as rprint\n\nsys.path.insert(0, str(Path(__file__).parent))\n\nimport db\nfrom crawlers.aggregator import collect_all_jobs\nfrom agents.analyst import analyze_jd, score_job, profile_company\nfrom agents.tailor import generate_tailored_resume, render_latex, generate_greeting\nfrom agents.coach import generate_interview_pack\n\nconsole = Console()\nOUTPUT_DIR = Path(__file__).parent / \"data\" / \"outputs\"\n\n\ndef step_collect(keyword: str = \"AI Agent\", location: str = \"武汉\",\n platforms: list[str] | None = None) -> list[dict]:\n \"\"\"Step 1: Collect jobs from all platforms.\"\"\"\n console.print(\"\\n[bold cyan]═══ Step 1: 岗位采集(多平台真实爬虫) ═══[/]\")\n\n platform_names = \", \".join(platforms) if platforms else \"boss + nowcoder + liepin + curated\"\n console.print(f\" 平台: {platform_names}\")\n\n jobs = collect_all_jobs(keyword, location, platforms=platforms)\n console.print(f\"[green]✓[/] 采集到 {len(jobs)} 个岗位\")\n\n table = Table(title=\"采集结果\", show_lines=True)\n table.add_column(\"#\", style=\"dim\", width=4)\n table.add_column(\"公司\", style=\"bold\")\n table.add_column(\"岗位\")\n table.add_column(\"地点\")\n table.add_column(\"薪资\")\n table.add_column(\"类型\")\n table.add_column(\"平台\")\n\n for i, j in enumerate(jobs, 1):\n table.add_row(\n str(i), j[\"company\"], j[\"title\"], j.get(\"location\", \"\"),\n j.get(\"salary\", \"\"), j.get(\"job_type\", \"\"), j[\"platform\"],\n )\n\n console.print(table)\n return jobs\n\n\ndef step_analyze_and_score(jobs: list[dict]) -> list[dict]:\n \"\"\"Step 2: Analyze JDs and score each job.\"\"\"\n console.print(\"\\n[bold cyan]═══ Step 2: 岗位分析 & 10维评分 ═══[/]\")\n\n scored = []\n for i, job in enumerate(jobs):\n console.print(f\" [{i+1}/{len(jobs)}] 分析: {job['company']} - {job['title']}\")\n\n jd_text = f\"岗位: {job['title']}\\n公司: {job['company']}\\n地点: {job.get('location','')}\\n薪资: {job.get('salary','')}\\n描述: {job.get('description','')}\\n要求: {job.get('requirements','')}\\n技能: {job.get('skills','')}\"\n try:\n jd_info = analyze_jd(jd_text)\n total, details = score_job(jd_info)\n job[\"jd_info\"] = jd_info\n job[\"score\"] = total\n job[\"score_details\"] = details\n\n color = \"green\" if total >= 0.6 else \"yellow\" if total >= 0.4 else \"red\"\n console.print(f\" [{color}]评分: {total:.2f}[/] | {details.get('reasoning','')[:60]}\")\n\n job_db_id = job.get(\"db_id\")\n if job_db_id:\n db.update_job_score(job_db_id, total, details)\n except Exception as e:\n console.print(f\" [red]分析失败: {e}[/]\")\n job[\"score\"] = 0\n job[\"jd_info\"] = {}\n\n scored.append(job)\n time.sleep(1)\n\n scored.sort(key=lambda x: x.get(\"score\", 0), reverse=True)\n\n table = Table(title=\"评分排名\", show_lines=True)\n table.add_column(\"#\", style=\"dim\")\n table.add_column(\"公司\", style=\"bold\")\n table.add_column(\"岗位\")\n table.add_column(\"评分\", justify=\"right\")\n table.add_column(\"推荐\", justify=\"center\")\n\n for i, j in enumerate(scored):\n s = j.get(\"score\", 0)\n rec = \"✅ 强推\" if s >= 0.65 else \"⚡ 可投\" if s >= 0.45 else \"❌ 跳过\"\n color = \"green\" if s >= 0.65 else \"yellow\" if s >= 0.45 else \"red\"\n table.add_row(str(i+1), j[\"company\"], j[\"title\"], f\"[{color}]{s:.2f}[/]\", rec)\n\n console.print(table)\n return scored\n\n\ndef step_export(jobs: list[dict]) -> Path | None:\n \"\"\"Step 2.5: Export jobs to Excel.\"\"\"\n console.print(\"\\n[bold cyan]═══ Step 2.5: 导出 Excel ═══[/]\")\n try:\n from export.excel import export_excel\n ts = datetime.now().strftime(\"%Y%m%d_%H%M\")\n path = export_excel(jobs, OUTPUT_DIR / f\"jobs_{ts}.xlsx\")\n console.print(f\"[green]✓[/] Excel 导出: {path}\")\n return path\n except Exception as e:\n console.print(f\"[yellow]⚠ Excel 导出失败: {e}[/]\")\n return None\n\n\ndef step_company_profile(job: dict) -> dict:\n \"\"\"Step 3: Generate company profile.\"\"\"\n console.print(f\"\\n[bold cyan]═══ Step 3: 公司画像 - {job['company']} ═══[/]\")\n try:\n profile = profile_company(job[\"company\"])\n console.print(Panel(\n f\"[bold]{profile.get('name', job['company'])}[/]\\n\"\n f\"行业: {profile.get('industry', '未知')}\\n\"\n f\"规模: {profile.get('size', '未知')}\\n\"\n f\"评分: {profile.get('rating', 'N/A')}/10\\n\"\n f\"优点: {', '.join(profile.get('pros', []))}\\n\"\n f\"缺点: {', '.join(profile.get('cons', []))}\\n\"\n f\"薪资: {profile.get('avg_salary', '未知')}\\n\"\n f\"加班: {profile.get('work_life_balance', '未知')}\",\n title=\"公司画像\",\n ))\n return profile\n except Exception as e:\n console.print(f\"[red]公司画像生成失败: {e}[/]\")\n return {}\n\n\ndef step_generate_resume(job: dict) -> tuple[dict, Path | None]:\n \"\"\"Step 4: Generate tailored resume.\"\"\"\n console.print(f\"\\n[bold cyan]═══ Step 4: 简历定制 - {job['company']} {job['title']} ═══[/]\")\n\n jd_info = job.get(\"jd_info\", {})\n if not jd_info:\n jd_info = {\"title\": job[\"title\"], \"company\": job[\"company\"],\n \"required_skills\": job.get(\"requirements\", \"\").split(\";\")}\n\n try:\n resume_data = generate_tailored_resume(jd_info)\n resume_data.setdefault(\"basics_location\", \"武汉\")\n\n console.print(\"[green]✓[/] 简历内容生成完成\")\n console.print(f\" 个人总结: {resume_data.get('summary', '')[:100]}...\")\n console.print(f\" 项目数量: {len(resume_data.get('projects', []))}\")\n console.print(f\" ATS关键词: {', '.join(resume_data.get('keywords_injected', []))}\")\n\n safe_name = f\"{job['company']}_{job['title']}\".replace(\"/\", \"_\").replace(\" \", \"_\")[:50]\n output_name = f\"resume_{safe_name}_{datetime.now().strftime('%Y%m%d')}\"\n\n try:\n pdf_path = render_latex(resume_data, output_name)\n console.print(f\"[green]✓[/] PDF生成: {pdf_path}\")\n except Exception as e:\n console.print(f\"[yellow]⚠ LaTeX编译失败 (可能未安装): {e}[/]\")\n md_path = OUTPUT_DIR / f\"{output_name}.json\"\n md_path.write_text(json.dumps(resume_data, ensure_ascii=False, indent=2), encoding=\"utf-8\")\n console.print(f\"[green]✓[/] 简历数据已保存: {md_path}\")\n pdf_path = None\n\n greeting = generate_greeting(jd_info, resume_data)\n console.print(Panel(greeting, title=\"Boss直聘打招呼语\"))\n\n return resume_data, pdf_path\n except Exception as e:\n console.print(f\"[red]简历生成失败: {e}[/]\")\n return {}, None\n\n\ndef step_interview_prep(job: dict, resume_data: dict | None = None) -> dict:\n \"\"\"Step 5: Generate interview preparation package.\"\"\"\n console.print(f\"\\n[bold cyan]═══ Step 5: 面试资料生成 - {job['company']} {job['title']} ═══[/]\")\n\n jd_info = job.get(\"jd_info\", {})\n if not jd_info:\n jd_info = {\"title\": job[\"title\"], \"company\": job[\"company\"],\n \"required_skills\": job.get(\"requirements\", \"\").split(\";\")}\n\n try:\n console.print(\" [dim]生成技能树...[/]\")\n pack = generate_interview_pack(jd_info, resume_data)\n\n safe_name = f\"{job['company']}_{job['title']}\".replace(\"/\", \"_\").replace(\" \", \"_\")[:50]\n base_name = f\"interview_{safe_name}_{datetime.now().strftime('%Y%m%d')}\"\n\n OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n\n skill_path = OUTPUT_DIR / f\"{base_name}_skill_tree.json\"\n skill_path.write_text(json.dumps(pack[\"skill_tree\"], ensure_ascii=False, indent=2), encoding=\"utf-8\")\n\n study_path = OUTPUT_DIR / f\"{base_name}_study_path.md\"\n study_path.write_text(pack[\"study_path\"], encoding=\"utf-8\")\n\n eight_path = OUTPUT_DIR / f\"{base_name}_eight_part.md\"\n eight_path.write_text(pack[\"eight_part_essay\"], encoding=\"utf-8\")\n\n mock_path = OUTPUT_DIR / f\"{base_name}_mock_interview.md\"\n mock_path.write_text(pack[\"mock_questions\"], encoding=\"utf-8\")\n\n console.print(f\"[green]✓[/] 技能树: {skill_path}\")\n console.print(f\"[green]✓[/] 学习路径: {study_path}\")\n console.print(f\"[green]✓[/] 八股文: {eight_path}\")\n console.print(f\"[green]✓[/] 模拟面试: {mock_path}\")\n\n return pack\n except Exception as e:\n console.print(f\"[red]面试资料生成失败: {e}[/]\")\n return {}\n\n\ndef run_pipeline(keyword: str = \"AI Agent\", location: str = \"武汉\", top_n: int = 3,\n platforms: list[str] | None = None):\n \"\"\"Run the full end-to-end pipeline.\"\"\"\n console.print(Panel(\n f\"[bold]JobOS AI 求职全能工具[/]\\n\"\n f\"关键词: {keyword} | 地点: {location} | Top-N: {top_n}\\n\"\n f\"时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}\",\n title=\"🔥 Pipeline Start\",\n border_style=\"cyan\",\n ))\n\n jobs = step_collect(keyword, location, platforms)\n if not jobs:\n console.print(\"[red]没有采集到岗位,结束。[/]\")\n return\n\n scored_jobs = step_analyze_and_score(jobs)\n\n step_export(scored_jobs)\n\n top_jobs = [j for j in scored_jobs if j.get(\"score\", 0) > 0][:top_n]\n if not top_jobs:\n console.print(\"[yellow]没有高分岗位,取前3个继续处理。[/]\")\n top_jobs = scored_jobs[:top_n]\n\n console.print(f\"\\n[bold magenta]═══ 对 Top-{len(top_jobs)} 岗位执行完整流程 ═══[/]\")\n\n for i, job in enumerate(top_jobs):\n console.print(f\"\\n{'='*60}\")\n console.print(f\"[bold]目标 {i+1}: {job['company']} - {job['title']}[/]\")\n console.print(f\"{'='*60}\")\n\n step_company_profile(job)\n resume_data, pdf_path = step_generate_resume(job)\n step_interview_prep(job, resume_data)\n\n time.sleep(2)\n\n console.print(Panel(\n f\"[bold green]Pipeline 完成![/]\\n\"\n f\"处理了 {len(top_jobs)} 个岗位\\n\"\n f\"输出目录: {OUTPUT_DIR}\",\n title=\"✅ Pipeline Complete\",\n border_style=\"green\",\n ))\n\n\ndef run_single(job_data: dict):\n \"\"\"Run pipeline for a single specified job.\"\"\"\n console.print(Panel(\n f\"[bold]单岗位模式: {job_data['company']} - {job_data['title']}[/]\",\n title=\"🎯 Single Job\",\n border_style=\"cyan\",\n ))\n\n jd_text = f\"岗位: {job_data['title']}\\n公司: {job_data['company']}\\n描述: {job_data.get('description','')}\\n要求: {job_data.get('requirements','')}\"\n jd_info = analyze_jd(jd_text)\n job_data[\"jd_info\"] = jd_info\n\n total, details = score_job(jd_info)\n job_data[\"score\"] = total\n job_data[\"score_details\"] = details\n console.print(f\"[green]评分: {total:.2f}[/]\")\n\n step_company_profile(job_data)\n resume_data, _ = step_generate_resume(job_data)\n step_interview_prep(job_data, resume_data)\n\n\nif __name__ == \"__main__\":\n import argparse\n parser = argparse.ArgumentParser(description=\"JobOS Pipeline\")\n parser.add_argument(\"--keyword\", default=\"AI Agent\", help=\"搜索关键词\")\n parser.add_argument(\"--location\", default=\"武汉\", help=\"目标城市\")\n parser.add_argument(\"--top\", type=int, default=3, help=\"处理前N个岗位\")\n parser.add_argument(\"--platform\", nargs=\"*\", default=None,\n help=\"指定平台 (boss nowcoder liepin curated)\")\n args = parser.parse_args()\n\n run_pipeline(args.keyword, args.location, args.top, args.platform)\n"}}, "run_crawl_and_export.py": {"48a02c6eaefb2b8d116e41538a252fa9fc599f3b": {"t": 1785604071.2489908, "n": 4851, "h": "6f1f0c6b80fcc995", "c": "\"\"\"One-shot script: crawl all platforms for Wuhan AI jobs + export Excel.\"\"\"\n\nimport sys\nimport os\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent))\nos.chdir(Path(__file__).parent)\n\nimport logging\nlogging.basicConfig(level=logging.INFO, format=\"%(name)s | %(message)s\")\n\nfrom rich.console import Console\nfrom rich.table import Table\n\nconsole = Console()\n\ndef main():\n import db\n console.print(\"[bold cyan]═══ JobOS 岗位采集 + Excel 导出 ═══[/]\\n\")\n\n # --- 1. Crawl from all platforms ---\n all_jobs = []\n\n # Boss直聘 (real crawler first, fallback to curated)\n console.print(\"[bold]1. Boss直聘 (boss-cli → 策展数据 fallback)...[/]\")\n try:\n from crawlers.boss_real import search_boss_real\n boss_jobs = search_boss_real(\"AI Agent\", \"武汉\")\n if boss_jobs:\n console.print(f\" [green]boss-cli: {len(boss_jobs)} 个岗位[/]\")\n else:\n raise RuntimeError(\"boss-cli returned empty\")\n except Exception as e:\n console.print(f\" [yellow]boss-cli unavailable ({e}), using curated data[/]\")\n from crawlers.boss import search_boss_jobs\n boss_jobs = search_boss_jobs(\"AI Agent\", \"武汉\")\n console.print(f\" [green]策展数据: {len(boss_jobs)} 个岗位[/]\")\n all_jobs.extend(boss_jobs)\n\n # 牛客网\n console.print(\"\\n[bold]2. 牛客网 (requests)...[/]\")\n try:\n from crawlers.nowcoder import search_nowcoder\n nc_jobs = search_nowcoder(\"AI Agent\", \"武汉\", max_pages=2)\n console.print(f\" [green]{len(nc_jobs)} 个岗位[/]\")\n all_jobs.extend(nc_jobs)\n except Exception as e:\n console.print(f\" [red]牛客网失败: {e}[/]\")\n\n # 猎聘\n console.print(\"\\n[bold]3. 猎聘 (requests)...[/]\")\n try:\n from crawlers.liepin import search_liepin\n lp_jobs = search_liepin(\"AI Agent\", \"武汉\", max_pages=2)\n console.print(f\" [green]{len(lp_jobs)} 个岗位[/]\")\n all_jobs.extend(lp_jobs)\n except Exception as e:\n console.print(f\" [red]猎聘失败: {e}[/]\")\n\n # --- 2. Dedup ---\n seen = set()\n unique = []\n for j in all_jobs:\n key = f\"{j.get('company','').lower()}|{j.get('title','').lower()}\"\n if key not in seen:\n seen.add(key)\n unique.append(j)\n console.print(f\"\\n[bold]去重: {len(all_jobs)} → {len(unique)} 个岗位[/]\")\n\n # --- 3. Store in DB ---\n for j in unique:\n row_id = db.insert_job(\n platform=j.get(\"platform\", \"unknown\"),\n title=j.get(\"title\", \"\"),\n company=j.get(\"company\", \"\"),\n job_id=j.get(\"job_id\", \"\"),\n location=j.get(\"location\", \"\"),\n salary=j.get(\"salary\", \"\"),\n job_type=j.get(\"job_type\", \"\"),\n description=j.get(\"description\", \"\"),\n requirements=j.get(\"requirements\", \"\"),\n url=j.get(\"url\", \"\"),\n posted_date=j.get(\"posted_date\", \"\"),\n skills=j.get(\"skills\", \"\"),\n degree=j.get(\"degree\", \"\"),\n experience=j.get(\"experience\", \"\"),\n company_size=j.get(\"company_size\", \"\"),\n company_industry=j.get(\"company_industry\", \"\"),\n company_stage=j.get(\"company_stage\", \"\"),\n welfare=j.get(\"welfare\", \"\"),\n hr_name=j.get(\"hr_name\", \"\"),\n hr_title=j.get(\"hr_title\", \"\"),\n chat_url=j.get(\"chat_url\", \"\"),\n full_jd=j.get(\"full_jd\", \"\"),\n deadline=j.get(\"deadline\", \"\"),\n source_url=j.get(\"source_url\", \"\"),\n )\n if row_id:\n j[\"db_id\"] = row_id\n\n # --- 4. Print table ---\n table = Table(title=f\"武汉 AI 岗位采集结果 ({len(unique)} 个)\", show_lines=True)\n table.add_column(\"#\", style=\"dim\", width=4)\n table.add_column(\"来源\", width=8)\n table.add_column(\"公司\", style=\"bold\")\n table.add_column(\"岗位\")\n table.add_column(\"地点\")\n table.add_column(\"薪资\")\n table.add_column(\"类型\")\n\n for i, j in enumerate(unique, 1):\n table.add_row(\n str(i), j[\"platform\"], j[\"company\"], j[\"title\"],\n j.get(\"location\", \"\"), j.get(\"salary\", \"\"),\n j.get(\"job_type\", \"\"),\n )\n console.print(table)\n\n # --- 5. Export Excel ---\n console.print(\"\\n[bold]导出 Excel...[/]\")\n from export.excel import export_excel\n xlsx_path = export_excel(unique, \"data/outputs/wuhan_ai_jobs.xlsx\")\n console.print(f\"[bold green]✓ Excel 导出: {xlsx_path}[/]\")\n\n # --- 6. Export CSV + JSON ---\n from export.csv_export import export_csv\n from export.json_export import export_json\n csv_path = export_csv(unique, \"data/outputs/wuhan_ai_jobs.csv\")\n json_path = export_json(unique, \"data/outputs/wuhan_ai_jobs.json\")\n console.print(f\"[green]✓ CSV: {csv_path}[/]\")\n console.print(f\"[green]✓ JSON: {json_path}[/]\")\n\n console.print(f\"\\n[bold green]完成!共 {len(unique)} 个岗位,来自 Boss直聘 + 牛客网 + 猎聘[/]\")\n return unique\n\nif __name__ == \"__main__\":\n main()\n"}}, "run_hefei_live.py": {"e207b6ae9b76158a03db71e7f29695aae3ca6b23": {"t": 1785604073.5784671, "n": 8561, "h": "e920cad121bcd00d", "c": "\"\"\"\nJobOS — Boss直聘实时爬取(DrissionPage 浏览器方案)\n流程: 弹出浏览器 → 自动检测登录(未登录则扫码) → 爬取岗位 → 导出Excel\n\n用法:\n python run_hefei_live.py # 默认: 合肥 AI\n python run_hefei_live.py 武汉 \"AI Agent\" # 自定义: 武汉 AI Agent\n python run_hefei_live.py 北京 大模型 8 # 北京 大模型 爬8页\n\"\"\"\nimport sys\nimport os\nimport time\nimport json\nimport random\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent))\nos.chdir(Path(__file__).parent)\n\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.panel import Panel\nconsole = Console()\n\nCITY_CODES = {\n \"武汉\": \"101200100\", \"北京\": \"101010100\", \"上海\": \"101020100\",\n \"杭州\": \"101210100\", \"深圳\": \"101280600\", \"广州\": \"101280100\",\n \"成都\": \"101270100\", \"南京\": \"101190100\", \"合肥\": \"101220100\",\n \"西安\": \"101110100\", \"重庆\": \"101040100\", \"天津\": \"101030100\",\n \"苏州\": \"101190400\", \"厦门\": \"101230200\", \"长沙\": \"101250100\",\n \"青岛\": \"101120200\", \"郑州\": \"101180100\",\n}\n\nCITY = sys.argv[1] if len(sys.argv) > 1 else \"合肥\"\nKEYWORD = sys.argv[2] if len(sys.argv) > 2 else \"AI\"\nMAX_PAGES = int(sys.argv[3]) if len(sys.argv) > 3 else 5\nCITY_CODE = CITY_CODES.get(CITY, \"101220100\")\nUSER_DATA = str(Path(__file__).parent / \"data\" / \".boss_browser_profile\")\n\n\ndef main():\n console.print(Panel(\n f\"[bold]JobOS — {CITY} {KEYWORD} 岗位实时采集[/]\\n\"\n \"即将打开浏览器,请扫码登录 Boss直聘\",\n border_style=\"cyan\"\n ))\n\n from DrissionPage import ChromiumPage, ChromiumOptions\n co = ChromiumOptions()\n co.set_user_data_path(USER_DATA)\n co.set_argument(\"--disable-blink-features=AutomationControlled\")\n co.set_argument(\"--no-sandbox\")\n\n page = ChromiumPage(co)\n\n # --- 打开登录页,检测是否已登录 ---\n console.print(\"\\n[bold]Step 1: 检测登录状态...[/]\")\n page.get(\"https://www.zhipin.com/web/geek/job\")\n time.sleep(3)\n\n is_logged_in = _check_login(page)\n\n if not is_logged_in:\n console.print(\"[yellow]未登录,打开扫码页...[/]\")\n page.get(\"https://www.zhipin.com/web/user/?ka=header-login\")\n time.sleep(2)\n\n console.print(\"\\n[bold yellow]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/]\")\n console.print(\"[bold yellow] ⬆ 请在弹出的浏览器窗口里扫码登录 ⬆ [/]\")\n console.print(\"[bold yellow]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/]\")\n\n # 等待登录完成(最多120秒)\n for i in range(60):\n time.sleep(2)\n if _check_login(page):\n console.print(f\"\\n[bold green]✓ 登录成功![/]\")\n break\n if i % 5 == 0 and i > 0:\n console.print(f\" [dim]等待登录... ({i*2}秒)[/]\")\n else:\n console.print(\"[red]登录超时,请重新运行[/]\")\n page.quit()\n return\n else:\n console.print(\"[green]✓ 已登录[/]\")\n\n # --- 爬取岗位 ---\n console.print(f\"\\n[bold]Step 2: 爬取 {CITY} {KEYWORD} 岗位...[/]\")\n all_jobs = _crawl_jobs(page)\n\n page.quit()\n\n if not all_jobs:\n console.print(\"[red]没有爬到岗位,请检查网络或重新登录[/]\")\n return\n\n # --- 去重 ---\n seen = set()\n unique = []\n for j in all_jobs:\n key = f\"{j.get('company','').lower()}|{j.get('title','').lower()}\"\n if key not in seen:\n seen.add(key)\n unique.append(j)\n console.print(f\"[bold]去重: {len(all_jobs)} → {len(unique)} 个岗位[/]\")\n\n # --- 存数据库 ---\n import db\n for j in unique:\n db.insert_job(**{k: j.get(k, \"\") for k in [\n \"platform\",\"title\",\"company\",\"job_id\",\"location\",\"salary\",\n \"job_type\",\"description\",\"requirements\",\"url\",\"posted_date\",\n \"skills\",\"degree\",\"experience\",\"company_size\",\"company_industry\",\n \"company_stage\",\"welfare\",\"hr_name\",\"hr_title\",\"chat_url\",\n \"full_jd\",\"source_url\",\n ]})\n\n # --- 打印表格 ---\n table = Table(title=f\"{CITY} {KEYWORD} 实时采集结果 ({len(unique)}个)\", show_lines=True)\n table.add_column(\"#\", width=4, style=\"dim\")\n table.add_column(\"公司\", style=\"bold\")\n table.add_column(\"岗位\")\n table.add_column(\"薪资\")\n table.add_column(\"地点\")\n table.add_column(\"HR\")\n for i, j in enumerate(unique, 1):\n table.add_row(str(i), j[\"company\"], j[\"title\"],\n j.get(\"salary\",\"\"), j.get(\"location\",\"\"),\n f\"{j.get('hr_name','')} {j.get('hr_title','')}\".strip())\n console.print(table)\n\n # --- 导出 Excel ---\n from export.excel import export_excel\n from datetime import datetime\n ts = datetime.now().strftime(\"%Y%m%d_%H%M\")\n out_path = Path(f\"data/outputs/{CITY}_{KEYWORD}_jobs_{ts}.xlsx\")\n path = export_excel(unique, out_path)\n console.print(f\"\\n[bold green]✓ Excel 已导出: {path}[/]\")\n\n # 自动打开 Excel\n os.system(f\"open '{path}'\")\n console.print(\"[dim]已自动打开 Excel 文件[/]\")\n\n\ndef _check_login(page) -> bool:\n \"\"\"Check if user is logged in by looking for avatar/username elements.\"\"\"\n try:\n url = page.url\n # If redirected to login page, not logged in\n if \"user/?ka=header-login\" in url or \"login\" in url:\n return False\n # Check for login indicators in page\n el = page.ele(\"css:.nav-figure img, css:.user-nav .figure, css:[class*='nav-info']\", timeout=3)\n return el is not None\n except Exception:\n return False\n\n\ndef _crawl_jobs(page) -> list[dict]:\n \"\"\"Crawl job listings by intercepting API responses.\"\"\"\n all_jobs = []\n url = f\"https://www.zhipin.com/web/geek/job?query={KEYWORD}&city={CITY_CODE}\"\n\n for pg in range(1, MAX_PAGES + 1):\n try:\n page.listen.start(\"wapi/zpgeek/search/joblist\")\n target = url if pg == 1 else f\"{url}&page={pg}\"\n page.get(target)\n\n console.print(f\" [dim]第 {pg}/{MAX_PAGES} 页...[/]\", end=\"\")\n\n try:\n packet = page.listen.wait(timeout=20)\n except Exception:\n console.print(\" [yellow]超时[/]\")\n break\n\n page.listen.stop()\n\n if not packet or not packet.response or not packet.response.body:\n console.print(\" [yellow]无数据[/]\")\n break\n\n body = packet.response.body\n if isinstance(body, str):\n body = json.loads(body)\n\n if body.get(\"code\") != 0:\n console.print(f\" [red]API错误 {body.get('code')}[/]\")\n break\n\n job_list = body.get(\"zpData\", {}).get(\"jobList\", [])\n if not job_list:\n console.print(\" [yellow]没有更多岗位[/]\")\n break\n\n for j in job_list:\n encrypt_boss_id = j.get(\"encryptBossId\", \"\")\n security_id = j.get(\"securityId\", \"\")\n chat_url = f\"https://www.zhipin.com/web/geek/chat?id={encrypt_boss_id}&securityId={security_id}\" if encrypt_boss_id and security_id else \"\"\n\n all_jobs.append({\n \"platform\": \"boss\",\n \"job_id\": j.get(\"encryptJobId\", \"\"),\n \"title\": j.get(\"jobName\", \"\"),\n \"company\": j.get(\"brandName\", \"\"),\n \"location\": f\"{j.get('cityName','')} {j.get('areaDistrict','')} {j.get('businessDistrict','')}\".strip(),\n \"salary\": j.get(\"salaryDesc\", \"\"),\n \"job_type\": \"实习\" if \"实习\" in \" \".join(j.get(\"jobLabels\", [])) else \"社招\",\n \"description\": j.get(\"jobName\", \"\"),\n \"requirements\": \"\",\n \"url\": f\"https://www.zhipin.com/job_detail/{j.get('encryptJobId','')}.html\",\n \"posted_date\": str(j.get(\"lastModifyTime\", \"\")),\n \"skills\": \",\".join(j.get(\"skills\", [])),\n \"degree\": j.get(\"jobDegree\", \"\"),\n \"experience\": j.get(\"jobExperience\", \"\"),\n \"company_size\": j.get(\"brandScaleName\", \"\"),\n \"company_industry\": j.get(\"brandIndustry\", \"\"),\n \"company_stage\": j.get(\"brandStageName\", \"\"),\n \"welfare\": \",\".join(j.get(\"welfareList\", [])),\n \"hr_name\": j.get(\"bossName\", \"\"),\n \"hr_title\": j.get(\"bossTitle\", \"\"),\n \"chat_url\": chat_url,\n \"full_jd\": \"\",\n \"source_url\": f\"https://www.zhipin.com/job_detail/{j.get('encryptJobId','')}.html\",\n })\n\n console.print(f\" [green]{len(job_list)} 个岗位[/]\")\n\n has_more = body.get(\"zpData\", {}).get(\"hasMore\", False)\n if not has_more:\n break\n\n time.sleep(random.uniform(2.5, 4.5))\n\n except Exception as e:\n console.print(f\" [red]错误: {e}[/]\")\n break\n\n return all_jobs\n\n\nif __name__ == \"__main__\":\n main()\n"}}, "run_huawei_demo.py": {"b095e16a76d0965df0e6590ca7aebf5f543c6f24": {"t": 1785604074.8153424, "n": 2064, "h": "e7d085380a772a09", "c": "\"\"\"Demo: run full JobOS pipeline for 华为武汉AI实习.\"\"\"\n\nimport sys\nimport os\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path(__file__).parent))\nos.chdir(Path(__file__).parent)\n\nfrom rich.console import Console\nconsole = Console()\n\n\ndef main():\n from pipeline import (\n step_analyze_and_score, step_company_profile,\n step_generate_resume, step_interview_prep, step_export,\n )\n\n huawei_job = {\n \"platform\": \"boss\",\n \"job_id\": \"boss_hw_001\",\n \"title\": \"AI工程师实习生(大模型/NLP方向)\",\n \"company\": \"华为\",\n \"location\": \"武汉东湖高新区(华为武汉研究所)\",\n \"salary\": \"300-350元/天\",\n \"job_type\": \"暑期实习\",\n \"description\": \"参与华为云/盘古大模型相关的AI应用研发,包括大模型推理优化、NLP算法、Agent系统设计等方向。华为2026暑期实习Star计划\",\n \"requirements\": \"2027届硕士/博士;熟悉Python/C++;精通PyTorch;有LLM/NLP项目经验;熟悉Transformer架构;有顶会论文或竞赛获奖优先\",\n \"url\": \"https://career.huawei.com/reccampportal/portal5/campus-recruitment.html\",\n \"posted_date\": \"2026-03\",\n \"skills\": \"Python,C++,PyTorch,LLM,NLP,Transformer,Agent\",\n \"degree\": \"硕士/博士\",\n \"experience\": \"有LLM/NLP项目经验\",\n \"company_size\": \"100000+\",\n \"company_industry\": \"人工智能/通信/ICT\",\n \"company_stage\": \"上市公司\",\n \"welfare\": \"五险一金,免费班车,免费三餐,健身房\",\n \"source_url\": \"https://career.huawei.com/reccampportal/portal5/campus-recruitment.html\",\n }\n\n console.print(\"[bold cyan]═══ JobOS Demo: 华为武汉AI实习 完整流程 ═══[/]\\n\")\n\n # Step 1: Analyze & Score\n console.print(\"[bold]Step 1: 岗位分析 & 10维评分[/]\")\n scored = step_analyze_and_score([huawei_job])\n\n # Step 2: Company Profile\n console.print(\"\\n[bold]Step 2: 公司画像[/]\")\n step_company_profile(huawei_job)\n\n # Step 3: Resume\n console.print(\"\\n[bold]Step 3: 简历定制[/]\")\n resume_data, pdf_path = step_generate_resume(huawei_job)\n\n # Step 4: Interview Prep\n console.print(\"\\n[bold]Step 4: 面试资料生成[/]\")\n step_interview_prep(huawei_job, resume_data)\n\n console.print(\"\\n[bold green]═══ 华为武汉AI实习 Demo 完成!═══[/]\")\n console.print(\"输出目录: data/outputs/\")\n\n\nif __name__ == \"__main__\":\n main()\n"}}}} |