saitejatirunagari Claude Opus 4.8 commited on
Commit
43c3b77
·
1 Parent(s): c687f2b

feat: live V1 acceptance — independent audit, 2-parser PDF, summary opt, completeness

Browse files

Adds the pieces needed to prove the real production outcome (no mocks):
- ats_evaluate.py: independent post-generation audit (fresh extraction, scored
from PARSED PDF text) + acceptance_verdict (>=90 only if audit clean)
- pdf_validate.py: verify_keywords_two_parsers (pdftotext + pymupdf/pdfplumber);
every accepted keyword must survive BOTH parsers
- resume_rewrite.py: corpus-verified headline/SUMMARY optimization
(optimize_summary + verify_against_corpus) — reuses only résumé-corpus facts
- llm_client.py: rewrite_summary + extract correction_hint (completeness retry)
- nim_fallback.py: extraction_gaps() completeness check (named tools + years)
- ats_safe.py: wired completeness retry, summary optimization, reportlab ATS-safe
PDF fallback (always produces a parseable PDF), independent audit + 2-parser
verify (run_audit); résumé-source-derived section order for PDF validation
- ats_score.py: parsing gate hinges on recovered-text + no-hidden-markers
- api_server.py: blocking V1 route runs the audit
- scripts/accept_v1_live.py: live 3-case acceptance harness (real JDs in
data/acceptance/); real reportlab PDFs + 2-parser parse + audit

Deterministic suite: 50 passed / 1 skipped. Live run evidence pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

.gitignore CHANGED
@@ -40,3 +40,9 @@ data/logs/
40
 
41
  # Vendor (ever-jobs NestJS monorepo)
42
  vendor/
 
 
 
 
 
 
 
40
 
41
  # Vendor (ever-jobs NestJS monorepo)
42
  vendor/
43
+
44
+ # unrelated embedded project + local artifacts
45
+ Job search Git project - Everjobs/
46
+ src/form_autofill.py
47
+ tests/v2_output.pdf
48
+ data/acceptance/out/
api_server.py CHANGED
@@ -217,6 +217,7 @@ def latex_flow_for_api(
217
  company=company or "",
218
  job_title=company or job_title or "resume",
219
  llm_client=llm, selected_model=sel, model_health=health,
 
220
  out_dir=out_dir,
221
  compile_pdf=True,
222
  )
 
217
  company=company or "",
218
  job_title=company or job_title or "resume",
219
  llm_client=llm, selected_model=sel, model_health=health,
220
+ run_audit=True,
221
  out_dir=out_dir,
222
  compile_pdf=True,
223
  )
data/acceptance/jd1_stripe_payments.txt ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SOURCE_URL: https://weworkremotely.com/remote-jobs/stripe-staff-product-manager-payments
2
+
3
+ Who we are
4
+ About Stripe
5
+
6
+ Stripe is a financial infrastructure platform for businesses. Millions of companies - from the world's largest enterprises to the most ambitious startups - use Stripe to accept payments, grow their revenue, and accelerate new business opportunities. Our mission is to increase the GDP of the internet, and we have a staggering amount of work ahead. That means you have an unprecedented opportunity to put the global economy within everyone's reach while doing the most important work of your career.
7
+
8
+ About the Organization
9
+
10
+ The Payments organization focuses on developing products and platforms that enable users to accept payments from customers efficiently. This includes building APIs for processing payments, enabling regional, non-card payment options, and extending Stripe's capabilities to make it easy for businesses to accept in-person payments. Optimized Checkout and Link teams work to create best-in-class checkout experiences that enhance customer satisfaction and drive merchant conversion rates.
11
+
12
+ Team Matching: exact team matching for one of the subteams within this org will begin during final stages. Please note we may also consider you for different orgs based on your experience, location, etc. More information on our team matching process can be found here.
13
+
14
+ Regardless of team, you will own a critical piece of Stripe's payments stack, define product strategy that balances user needs with infrastructure complexity, and ship products that directly impact Stripe's revenue and the success of millions of businesses.
15
+
16
+ What you'll do
17
+
18
+ We're looking for Staff Product Managers who deeply understand their customers and want to make an impact on managing money at a global scale. Our team collaborates with many cross-functional teams at Stripe to deliver innovative solutions that address evolving user needs.
19
+
20
+ Responsibilities
21
+ Develop the long-term vision and strategy for your pillar and create and execute on a compelling roadmap
22
+ Build deep user empathy with both Stripe's merchants and financial partners, understanding global payment industry trends and competitors' offerings to influence Stripe's roadmap effectively
23
+ Collaborate with engineering, design, legal, and other teams to jointly shape product experiences that delight Stripe's users
24
+ Ensure we are building a reliable, performant platform, including system reliability, API extensibility, and latency
25
+ Use metrics to inform your point of view and leverage analytics to measure success
26
+
27
+ Who you are
28
+
29
+ We're looking for someone who meets the minimum requirements to be considered for the role. If you meet these requirements, you are encouraged to apply. The preferred qualifications are a bonus, not a requirement.
30
+
31
+ Minimum requirements
32
+ 10+ years of industry product management experience (does not include internships nor includes co-ops)
33
+ Experience in payments, fintech, financial infrastructure, or a closely adjacent domain (e.g., commerce platforms, banking, payment networks) and a track record of building highly impactful products
34
+ Demonstrated experience partnering closely with engineers, designers, and external partners to build products and complex systems at scale
35
+ Strong written and verbal communication skills, with a knack for precise and concise articulation of user problems
36
+ Strong analytical capabilities with experience defining and tracking success metrics, running experiments (e.g., holdback tests), and using data to measure product impact on revenue, conversion, and margins
37
+ Experience working on products that require aligning multiple stakeholders, developing joint roadmaps, and navigating competing priorities between internal goals and external partner objectives
38
+ Ability to thrive in a dynamic and fast-paced environment with significant autonomy and responsibility
39
+ Background delivering successfully on multi-quarter roadmaps and aligning and influencing diverse stakeholders to achieve shared goals
40
+ Strong business acumen and comfort with complex ecosystem and platform-level problems
41
+ Preferred qualifications
42
+ Bachelor's degree (or relevant degree equivalent): Computer Science, Engineering, Information Systems, Analytics, Mathematics, Physics, Applied Sciences
43
+ Demonstrated success and strong execution track record in building payment products
44
+ Experience working at scale - you've managed products handling high transaction volumes where availability, latency, and marginal basis points matter
data/acceptance/jd2_stripe_genai.txt ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SOURCE_URL: https://weworkremotely.com/remote-jobs/stripe-staff-product-manager-ml-foundations-and-genai
2
+
3
+ Who we are
4
+ About Stripe
5
+
6
+ Stripe is a financial infrastructure platform for businesses. Millions of companies—from the world's largest enterprises to the most ambitious startups—use Stripe to accept payments, grow their revenue, and accelerate new business opportunities. Our mission is to increase the GDP of the internet, and we have a staggering amount of work ahead. That means you have an unprecedented opportunity to put the global economy within everyone's reach while doing the most important work of your career.
7
+
8
+ About the team
9
+
10
+ You will be joining Stripe's ML Foundations and Gen AI team to incubate new ML applications and improve our ML capabilities across Stripe. Our team is responsible for unlocking novel ML and LLM techniques and applications across Stripe's product suite to drive business outcomes, as well as providing infrastructure, tooling and support for ML teams.
11
+
12
+ What you'll do
13
+
14
+ As a senior product leader, you will lead a cross-functional team to define, incubate and scale new ML/AI applications across Stripe's product suite, and drive our strategy and roadmap for ML/AI infrastructure powering all of Stripe's teams. You will work closely with product leaders across business units to define and deliver on an AI-centric product strategy, launching new applications that drive incremental business outcomes. At the same time, you will be advancing our core AI technology stack to empower teams across Stripe to infuse their scenarios with Agents and agentic capabilities, with API support for agent quality and continuous improvement.
15
+
16
+ Responsibilities
17
+ Develop and execute on the Stripe-wide strategy for new ML/AI applications across our product suite
18
+ Evaluate and align on areas of investment for ML/AI applications in collaboration with product leaders across the company
19
+ Work with cross-functional teams to execute on the roadmap and launch successful new ML/AI applications
20
+ Communicate clearly and crisply with leadership stakeholders and drive alignment across multiple teams
21
+ Develop and execute on a strategy for advancing Stripe's ML/AI infrastructure and tooling
22
+ Who you are
23
+
24
+ We're looking for someone who meets the requirements below, and has a passion for AI to be considered for the role. If you meet these requirements, you are encouraged to apply. The preferred qualifications are a bonus, not a requirement.
25
+
26
+ Minimum requirements
27
+ 7+ years of experience delivering highly successful and innovative software products which are ML powered
28
+ Solid understanding of ML and applied AI tech stacks
29
+ Demonstrated ability to influence company level strategy and work with business leaders to execute on the transformation
30
+ You push the pace. You take blame and pass the praise. People love working with you.
31
+ Proven ability to lead teams and work cross-functionally in a highly collaborative environment.
32
+ Ability to analyze and use quantitative and qualitative data to inform decisions.
33
+ A deep understanding and empathy for consumer and business users — you love building products that make our customers feel joy, delight and trust.
34
+ Relentlessly drives product quality
35
+ Capable of working on both 1P and 3P products
data/acceptance/jd3_growth_marketing.txt ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SOURCE_URL: https://weworkremotely.com/remote-jobs/power-digital-growth-marketing-manager-b2b-saas
2
+
3
+ Who We Are:
4
+
5
+ We are a tech-enabled growth firm–at the intersection of marketing, consulting & data intelligence–igniting revenue and brand recognition for leading and emerging companies around the world. As a people-first firm, we value diversity in backgrounds and experiences. We strongly believe our people and culture are key to our success.
6
+
7
+ As a full-service growth marketing firm, we offer best-in-class services including: SEO, Content Marketing, Paid Media, Social Media Marketing, Programmatic + CTV, Public Relations, Influencer Marketing, Email + SMS, Conversion Rate Optimization, Retail Marketing, and Creative.
8
+
9
+ At the heart of Power Digital is our proprietary technology, nova, which analyzes businesses through first-party data, simplifying investment planning for marketing and diligence in M&A.
10
+
11
+ A day in the life:
12
+
13
+ As a Growth Marketing Manager, you'll own the strategy and execution behind the integrated marketing campaigns that fuel Cardinal's pipeline. You'll architect full-funnel initiatives, from webinars and events to reports and eBooks, then lead cross-functional teams to bring each campaign to life with cohesive messaging and high-impact creative. Daily, you'll monitor performance, optimize in real time, and ensure that every channel works together to generate high-quality leads. You'll collaborate closely with internal contributors and contractors, keeping projects on track and jumping in to execute when needed.
14
+
15
+ Responsibilities:
16
+
17
+ Campaign Strategy & Planning
18
+ Build full-funnel campaign strategies for major launches (webinars, in-person events, eBooks, reports).
19
+ Define audiences, messaging, channel mix, goals, and success metrics.
20
+ Develop campaign plans and timelines aligned with growth and revenue priorities.
21
+ Integrated Activation
22
+ Lead cross-channel execution across email, paid media, social, and web.
23
+ Create creative briefs and coordinate with design, copy, video, paid, and email teams.
24
+ Ensure campaigns land with cohesive messaging, strong creative, and clear CTAs.
25
+ Performance & Optimization
26
+ Monitor performance and identify what's working, what's not, and why.
27
+ Optimize live campaigns—messaging, audience, UX, offers, funnel flow.
28
+ Build testing plans and share insights to improve results continually.
29
+ Collaboration & Leadership
30
+ Manage internal contributors and contractors, ensuring deadlines and quality.
31
+ Step in to execute when needed—no ego, high ownership.
32
+
33
+ Role Requirements:
34
+
35
+ Data-driven, audience-obsessed growth marketer.
36
+ Comfortable owning both strategy and execution.
37
+ Strong communicator across creative and performance teams.
38
+ 5–7 years in B2B growth, demand gen, or integrated marketing (agency/professional services a plus).
39
+ Demonstrated success running multi-channel, full-funnel campaigns with measurable results.
40
+ Expertise in LinkedIn Ads, Meta Ads, Google Ads, email marketing platforms, and webinar tools.
41
+ Deep understanding of digital channels and how they work together.
42
+ Ability to write or refine campaign copy (emails, landing pages, ads).
43
+ Strong analytical skills: reporting, performance diagnosis, and conversion optimization.
44
+ Experience thriving in fast-paced environments with high standards.
data/candidate_vault.json ADDED
@@ -0,0 +1,1732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "entries": [
3
+ {
4
+ "term": "product management",
5
+ "category": "hard_skill",
6
+ "source": "resume_original",
7
+ "confidence": "high",
8
+ "usage_guidance": "safe_to_use",
9
+ "example_bullet": ""
10
+ },
11
+ {
12
+ "term": "agile/scrum",
13
+ "category": "hard_skill",
14
+ "source": "inferred_plausible",
15
+ "confidence": "medium",
16
+ "usage_guidance": "use_carefully",
17
+ "example_bullet": ""
18
+ },
19
+ {
20
+ "term": "feedback",
21
+ "category": "hard_skill",
22
+ "source": "inferred_plausible",
23
+ "confidence": "medium",
24
+ "usage_guidance": "use_carefully",
25
+ "example_bullet": ""
26
+ },
27
+ {
28
+ "term": "user",
29
+ "category": "hard_skill",
30
+ "source": "resume_original",
31
+ "confidence": "high",
32
+ "usage_guidance": "safe_to_use",
33
+ "example_bullet": ""
34
+ },
35
+ {
36
+ "term": "competitor analysis",
37
+ "category": "hard_skill",
38
+ "source": "inferred_plausible",
39
+ "confidence": "medium",
40
+ "usage_guidance": "use_carefully",
41
+ "example_bullet": ""
42
+ },
43
+ {
44
+ "term": "product lifecycle",
45
+ "category": "hard_skill",
46
+ "source": "resume_original",
47
+ "confidence": "high",
48
+ "usage_guidance": "safe_to_use",
49
+ "example_bullet": ""
50
+ },
51
+ {
52
+ "term": "product manager",
53
+ "category": "hard_skill",
54
+ "source": "resume_original",
55
+ "confidence": "high",
56
+ "usage_guidance": "safe_to_use",
57
+ "example_bullet": ""
58
+ },
59
+ {
60
+ "term": "problem-solving",
61
+ "category": "hard_skill",
62
+ "source": "inferred_plausible",
63
+ "confidence": "medium",
64
+ "usage_guidance": "use_carefully",
65
+ "example_bullet": ""
66
+ },
67
+ {
68
+ "term": "product vision",
69
+ "category": "hard_skill",
70
+ "source": "resume_original",
71
+ "confidence": "high",
72
+ "usage_guidance": "safe_to_use",
73
+ "example_bullet": ""
74
+ },
75
+ {
76
+ "term": "data-driven",
77
+ "category": "hard_skill",
78
+ "source": "resume_original",
79
+ "confidence": "high",
80
+ "usage_guidance": "safe_to_use",
81
+ "example_bullet": ""
82
+ },
83
+ {
84
+ "term": "roadmap",
85
+ "category": "hard_skill",
86
+ "source": "resume_original",
87
+ "confidence": "high",
88
+ "usage_guidance": "safe_to_use",
89
+ "example_bullet": ""
90
+ },
91
+ {
92
+ "term": "kpis",
93
+ "category": "hard_skill",
94
+ "source": "resume_original",
95
+ "confidence": "high",
96
+ "usage_guidance": "safe_to_use",
97
+ "example_bullet": ""
98
+ },
99
+ {
100
+ "term": "integrations",
101
+ "category": "hard_skill",
102
+ "source": "resume_original",
103
+ "confidence": "high",
104
+ "usage_guidance": "safe_to_use",
105
+ "example_bullet": ""
106
+ },
107
+ {
108
+ "term": "metrics",
109
+ "category": "hard_skill",
110
+ "source": "inferred_plausible",
111
+ "confidence": "medium",
112
+ "usage_guidance": "use_carefully",
113
+ "example_bullet": ""
114
+ },
115
+ {
116
+ "term": "iteration",
117
+ "category": "hard_skill",
118
+ "source": "inferred_plausible",
119
+ "confidence": "medium",
120
+ "usage_guidance": "use_carefully",
121
+ "example_bullet": ""
122
+ },
123
+ {
124
+ "term": "customers",
125
+ "category": "hard_skill",
126
+ "source": "resume_original",
127
+ "confidence": "high",
128
+ "usage_guidance": "safe_to_use",
129
+ "example_bullet": ""
130
+ },
131
+ {
132
+ "term": "backend",
133
+ "category": "hard_skill",
134
+ "source": "inferred_plausible",
135
+ "confidence": "medium",
136
+ "usage_guidance": "use_carefully",
137
+ "example_bullet": ""
138
+ },
139
+ {
140
+ "term": "end-to-end",
141
+ "category": "hard_skill",
142
+ "source": "resume_original",
143
+ "confidence": "high",
144
+ "usage_guidance": "safe_to_use",
145
+ "example_bullet": ""
146
+ },
147
+ {
148
+ "term": "environment",
149
+ "category": "hard_skill",
150
+ "source": "inferred_plausible",
151
+ "confidence": "medium",
152
+ "usage_guidance": "use_carefully",
153
+ "example_bullet": ""
154
+ },
155
+ {
156
+ "term": "systems",
157
+ "category": "hard_skill",
158
+ "source": "resume_original",
159
+ "confidence": "high",
160
+ "usage_guidance": "safe_to_use",
161
+ "example_bullet": ""
162
+ },
163
+ {
164
+ "term": "design",
165
+ "category": "hard_skill",
166
+ "source": "resume_original",
167
+ "confidence": "high",
168
+ "usage_guidance": "safe_to_use",
169
+ "example_bullet": ""
170
+ },
171
+ {
172
+ "term": "strategy",
173
+ "category": "hard_skill",
174
+ "source": "resume_original",
175
+ "confidence": "high",
176
+ "usage_guidance": "safe_to_use",
177
+ "example_bullet": ""
178
+ },
179
+ {
180
+ "term": "alignment",
181
+ "category": "hard_skill",
182
+ "source": "inferred_plausible",
183
+ "confidence": "medium",
184
+ "usage_guidance": "use_carefully",
185
+ "example_bullet": ""
186
+ },
187
+ {
188
+ "term": "customer needs",
189
+ "category": "hard_skill",
190
+ "source": "inferred_plausible",
191
+ "confidence": "medium",
192
+ "usage_guidance": "use_carefully",
193
+ "example_bullet": ""
194
+ },
195
+ {
196
+ "term": "development",
197
+ "category": "hard_skill",
198
+ "source": "resume_original",
199
+ "confidence": "high",
200
+ "usage_guidance": "safe_to_use",
201
+ "example_bullet": ""
202
+ },
203
+ {
204
+ "term": "launch",
205
+ "category": "hard_skill",
206
+ "source": "resume_original",
207
+ "confidence": "high",
208
+ "usage_guidance": "safe_to_use",
209
+ "example_bullet": ""
210
+ },
211
+ {
212
+ "term": "product requirements",
213
+ "category": "hard_skill",
214
+ "source": "inferred_plausible",
215
+ "confidence": "medium",
216
+ "usage_guidance": "use_carefully",
217
+ "example_bullet": ""
218
+ },
219
+ {
220
+ "term": "quality",
221
+ "category": "hard_skill",
222
+ "source": "inferred_plausible",
223
+ "confidence": "medium",
224
+ "usage_guidance": "use_carefully",
225
+ "example_bullet": ""
226
+ },
227
+ {
228
+ "term": "engineering",
229
+ "category": "hard_skill",
230
+ "source": "resume_original",
231
+ "confidence": "high",
232
+ "usage_guidance": "safe_to_use",
233
+ "example_bullet": ""
234
+ },
235
+ {
236
+ "term": "teams",
237
+ "category": "hard_skill",
238
+ "source": "resume_original",
239
+ "confidence": "high",
240
+ "usage_guidance": "safe_to_use",
241
+ "example_bullet": ""
242
+ },
243
+ {
244
+ "term": "business",
245
+ "category": "hard_skill",
246
+ "source": "resume_original",
247
+ "confidence": "high",
248
+ "usage_guidance": "safe_to_use",
249
+ "example_bullet": ""
250
+ },
251
+ {
252
+ "term": "stakeholders",
253
+ "category": "hard_skill",
254
+ "source": "resume_original",
255
+ "confidence": "high",
256
+ "usage_guidance": "safe_to_use",
257
+ "example_bullet": ""
258
+ },
259
+ {
260
+ "term": "priorities",
261
+ "category": "hard_skill",
262
+ "source": "inferred_plausible",
263
+ "confidence": "medium",
264
+ "usage_guidance": "use_carefully",
265
+ "example_bullet": ""
266
+ },
267
+ {
268
+ "term": "performance",
269
+ "category": "hard_skill",
270
+ "source": "resume_original",
271
+ "confidence": "high",
272
+ "usage_guidance": "safe_to_use",
273
+ "example_bullet": ""
274
+ },
275
+ {
276
+ "term": "data",
277
+ "category": "hard_skill",
278
+ "source": "resume_original",
279
+ "confidence": "high",
280
+ "usage_guidance": "safe_to_use",
281
+ "example_bullet": ""
282
+ },
283
+ {
284
+ "term": "products",
285
+ "category": "hard_skill",
286
+ "source": "resume_original",
287
+ "confidence": "high",
288
+ "usage_guidance": "safe_to_use",
289
+ "example_bullet": ""
290
+ },
291
+ {
292
+ "term": "delivery",
293
+ "category": "hard_skill",
294
+ "source": "resume_original",
295
+ "confidence": "high",
296
+ "usage_guidance": "safe_to_use",
297
+ "example_bullet": ""
298
+ },
299
+ {
300
+ "term": "communicate",
301
+ "category": "hard_skill",
302
+ "source": "inferred_plausible",
303
+ "confidence": "medium",
304
+ "usage_guidance": "use_carefully",
305
+ "example_bullet": ""
306
+ },
307
+ {
308
+ "term": "decision-making",
309
+ "category": "hard_skill",
310
+ "source": "resume_original",
311
+ "confidence": "high",
312
+ "usage_guidance": "safe_to_use",
313
+ "example_bullet": ""
314
+ },
315
+ {
316
+ "term": "confluence",
317
+ "category": "tool",
318
+ "source": "inferred_plausible",
319
+ "confidence": "medium",
320
+ "usage_guidance": "use_carefully",
321
+ "example_bullet": ""
322
+ },
323
+ {
324
+ "term": "jira",
325
+ "category": "tool",
326
+ "source": "inferred_plausible",
327
+ "confidence": "medium",
328
+ "usage_guidance": "use_carefully",
329
+ "example_bullet": ""
330
+ },
331
+ {
332
+ "term": "market research",
333
+ "category": "responsibility",
334
+ "source": "resume_original",
335
+ "confidence": "high",
336
+ "usage_guidance": "safe_to_use",
337
+ "example_bullet": ""
338
+ },
339
+ {
340
+ "term": "edtech",
341
+ "category": "domain",
342
+ "source": "resume_original",
343
+ "confidence": "high",
344
+ "usage_guidance": "safe_to_use",
345
+ "example_bullet": ""
346
+ },
347
+ {
348
+ "term": "fintech",
349
+ "category": "domain",
350
+ "source": "inferred_plausible",
351
+ "confidence": "medium",
352
+ "usage_guidance": "use_carefully",
353
+ "example_bullet": ""
354
+ },
355
+ {
356
+ "term": "saas",
357
+ "category": "domain",
358
+ "source": "inferred_plausible",
359
+ "confidence": "medium",
360
+ "usage_guidance": "use_carefully",
361
+ "example_bullet": ""
362
+ },
363
+ {
364
+ "term": "degree in",
365
+ "category": "education",
366
+ "source": "inferred_plausible",
367
+ "confidence": "medium",
368
+ "usage_guidance": "use_carefully",
369
+ "example_bullet": ""
370
+ },
371
+ {
372
+ "term": "bachelor's",
373
+ "category": "education",
374
+ "source": "inferred_plausible",
375
+ "confidence": "medium",
376
+ "usage_guidance": "use_carefully",
377
+ "example_bullet": ""
378
+ },
379
+ {
380
+ "term": "stakeholder management",
381
+ "category": "soft_skill",
382
+ "source": "resume_original",
383
+ "confidence": "high",
384
+ "usage_guidance": "safe_to_use",
385
+ "example_bullet": ""
386
+ },
387
+ {
388
+ "term": "strategic thinking",
389
+ "category": "soft_skill",
390
+ "source": "inferred_plausible",
391
+ "confidence": "medium",
392
+ "usage_guidance": "use_carefully",
393
+ "example_bullet": ""
394
+ },
395
+ {
396
+ "term": "cross-functional",
397
+ "category": "soft_skill",
398
+ "source": "resume_original",
399
+ "confidence": "high",
400
+ "usage_guidance": "safe_to_use",
401
+ "example_bullet": ""
402
+ },
403
+ {
404
+ "term": "communication",
405
+ "category": "soft_skill",
406
+ "source": "resume_original",
407
+ "confidence": "high",
408
+ "usage_guidance": "safe_to_use",
409
+ "example_bullet": ""
410
+ },
411
+ {
412
+ "term": "leadership",
413
+ "category": "soft_skill",
414
+ "source": "resume_original",
415
+ "confidence": "high",
416
+ "usage_guidance": "safe_to_use",
417
+ "example_bullet": ""
418
+ },
419
+ {
420
+ "term": "collaboration",
421
+ "category": "soft_skill",
422
+ "source": "resume_original",
423
+ "confidence": "high",
424
+ "usage_guidance": "safe_to_use",
425
+ "example_bullet": ""
426
+ },
427
+ {
428
+ "term": "lead",
429
+ "category": "seniority",
430
+ "source": "resume_original",
431
+ "confidence": "high",
432
+ "usage_guidance": "safe_to_use",
433
+ "example_bullet": ""
434
+ },
435
+ {
436
+ "term": "7 years",
437
+ "category": "seniority",
438
+ "source": "blocked",
439
+ "confidence": "low",
440
+ "usage_guidance": "blocked",
441
+ "example_bullet": ""
442
+ },
443
+ {
444
+ "term": "threat intelligence",
445
+ "category": "hard_skill",
446
+ "source": "risky_review",
447
+ "confidence": "low",
448
+ "usage_guidance": "ask_user",
449
+ "example_bullet": ""
450
+ },
451
+ {
452
+ "term": "technologies",
453
+ "category": "hard_skill",
454
+ "source": "resume_original",
455
+ "confidence": "high",
456
+ "usage_guidance": "safe_to_use",
457
+ "example_bullet": ""
458
+ },
459
+ {
460
+ "term": "security operations",
461
+ "category": "hard_skill",
462
+ "source": "risky_review",
463
+ "confidence": "low",
464
+ "usage_guidance": "ask_user",
465
+ "example_bullet": ""
466
+ },
467
+ {
468
+ "term": "product development",
469
+ "category": "hard_skill",
470
+ "source": "resume_original",
471
+ "confidence": "high",
472
+ "usage_guidance": "safe_to_use",
473
+ "example_bullet": ""
474
+ },
475
+ {
476
+ "term": "threat detection",
477
+ "category": "hard_skill",
478
+ "source": "risky_review",
479
+ "confidence": "low",
480
+ "usage_guidance": "ask_user",
481
+ "example_bullet": ""
482
+ },
483
+ {
484
+ "term": "user experience",
485
+ "category": "hard_skill",
486
+ "source": "resume_original",
487
+ "confidence": "high",
488
+ "usage_guidance": "safe_to_use",
489
+ "example_bullet": ""
490
+ },
491
+ {
492
+ "term": "data analysis",
493
+ "category": "hard_skill",
494
+ "source": "inferred_plausible",
495
+ "confidence": "medium",
496
+ "usage_guidance": "use_carefully",
497
+ "example_bullet": ""
498
+ },
499
+ {
500
+ "term": "go-to-market",
501
+ "category": "hard_skill",
502
+ "source": "resume_original",
503
+ "confidence": "high",
504
+ "usage_guidance": "safe_to_use",
505
+ "example_bullet": ""
506
+ },
507
+ {
508
+ "term": "analytics",
509
+ "category": "hard_skill",
510
+ "source": "inferred_plausible",
511
+ "confidence": "medium",
512
+ "usage_guidance": "use_carefully",
513
+ "example_bullet": ""
514
+ },
515
+ {
516
+ "term": "prioritization",
517
+ "category": "hard_skill",
518
+ "source": "resume_original",
519
+ "confidence": "high",
520
+ "usage_guidance": "safe_to_use",
521
+ "example_bullet": ""
522
+ },
523
+ {
524
+ "term": "cloud",
525
+ "category": "hard_skill",
526
+ "source": "inferred_plausible",
527
+ "confidence": "medium",
528
+ "usage_guidance": "use_carefully",
529
+ "example_bullet": ""
530
+ },
531
+ {
532
+ "term": "engagement",
533
+ "category": "hard_skill",
534
+ "source": "resume_original",
535
+ "confidence": "high",
536
+ "usage_guidance": "safe_to_use",
537
+ "example_bullet": ""
538
+ },
539
+ {
540
+ "term": "use cases",
541
+ "category": "hard_skill",
542
+ "source": "inferred_plausible",
543
+ "confidence": "medium",
544
+ "usage_guidance": "use_carefully",
545
+ "example_bullet": ""
546
+ },
547
+ {
548
+ "term": "customer",
549
+ "category": "hard_skill",
550
+ "source": "resume_original",
551
+ "confidence": "high",
552
+ "usage_guidance": "safe_to_use",
553
+ "example_bullet": ""
554
+ },
555
+ {
556
+ "term": "dashboard",
557
+ "category": "hard_skill",
558
+ "source": "resume_original",
559
+ "confidence": "high",
560
+ "usage_guidance": "safe_to_use",
561
+ "example_bullet": ""
562
+ },
563
+ {
564
+ "term": "insights",
565
+ "category": "hard_skill",
566
+ "source": "inferred_plausible",
567
+ "confidence": "medium",
568
+ "usage_guidance": "use_carefully",
569
+ "example_bullet": ""
570
+ },
571
+ {
572
+ "term": "competitive",
573
+ "category": "hard_skill",
574
+ "source": "inferred_plausible",
575
+ "confidence": "medium",
576
+ "usage_guidance": "use_carefully",
577
+ "example_bullet": ""
578
+ },
579
+ {
580
+ "term": "vision",
581
+ "category": "hard_skill",
582
+ "source": "inferred_plausible",
583
+ "confidence": "medium",
584
+ "usage_guidance": "use_carefully",
585
+ "example_bullet": ""
586
+ },
587
+ {
588
+ "term": "requirements",
589
+ "category": "hard_skill",
590
+ "source": "inferred_plausible",
591
+ "confidence": "medium",
592
+ "usage_guidance": "use_carefully",
593
+ "example_bullet": ""
594
+ },
595
+ {
596
+ "term": "execution",
597
+ "category": "hard_skill",
598
+ "source": "resume_original",
599
+ "confidence": "high",
600
+ "usage_guidance": "safe_to_use",
601
+ "example_bullet": ""
602
+ },
603
+ {
604
+ "term": "features",
605
+ "category": "hard_skill",
606
+ "source": "resume_original",
607
+ "confidence": "high",
608
+ "usage_guidance": "safe_to_use",
609
+ "example_bullet": ""
610
+ },
611
+ {
612
+ "term": "scalable",
613
+ "category": "hard_skill",
614
+ "source": "inferred_plausible",
615
+ "confidence": "medium",
616
+ "usage_guidance": "use_carefully",
617
+ "example_bullet": ""
618
+ },
619
+ {
620
+ "term": "impact",
621
+ "category": "hard_skill",
622
+ "source": "inferred_plausible",
623
+ "confidence": "medium",
624
+ "usage_guidance": "use_carefully",
625
+ "example_bullet": ""
626
+ },
627
+ {
628
+ "term": "market",
629
+ "category": "hard_skill",
630
+ "source": "inferred_plausible",
631
+ "confidence": "medium",
632
+ "usage_guidance": "use_carefully",
633
+ "example_bullet": ""
634
+ },
635
+ {
636
+ "term": "reliability",
637
+ "category": "hard_skill",
638
+ "source": "inferred_plausible",
639
+ "confidence": "medium",
640
+ "usage_guidance": "use_carefully",
641
+ "example_bullet": ""
642
+ },
643
+ {
644
+ "term": "positioning",
645
+ "category": "hard_skill",
646
+ "source": "inferred_plausible",
647
+ "confidence": "medium",
648
+ "usage_guidance": "use_carefully",
649
+ "example_bullet": ""
650
+ },
651
+ {
652
+ "term": "operations platform",
653
+ "category": "hard_skill",
654
+ "source": "inferred_plausible",
655
+ "confidence": "medium",
656
+ "usage_guidance": "use_carefully",
657
+ "example_bullet": ""
658
+ },
659
+ {
660
+ "term": "product roadmap",
661
+ "category": "responsibility",
662
+ "source": "inferred_plausible",
663
+ "confidence": "medium",
664
+ "usage_guidance": "use_carefully",
665
+ "example_bullet": ""
666
+ },
667
+ {
668
+ "term": "siem",
669
+ "category": "domain",
670
+ "source": "risky_review",
671
+ "confidence": "low",
672
+ "usage_guidance": "ask_user",
673
+ "example_bullet": ""
674
+ },
675
+ {
676
+ "term": "xdr",
677
+ "category": "domain",
678
+ "source": "risky_review",
679
+ "confidence": "low",
680
+ "usage_guidance": "ask_user",
681
+ "example_bullet": ""
682
+ },
683
+ {
684
+ "term": "soar",
685
+ "category": "domain",
686
+ "source": "risky_review",
687
+ "confidence": "low",
688
+ "usage_guidance": "ask_user",
689
+ "example_bullet": ""
690
+ },
691
+ {
692
+ "term": "b2b",
693
+ "category": "domain",
694
+ "source": "inferred_plausible",
695
+ "confidence": "medium",
696
+ "usage_guidance": "use_carefully",
697
+ "example_bullet": ""
698
+ },
699
+ {
700
+ "term": "secops",
701
+ "category": "domain",
702
+ "source": "risky_review",
703
+ "confidence": "low",
704
+ "usage_guidance": "ask_user",
705
+ "example_bullet": ""
706
+ },
707
+ {
708
+ "term": "mba",
709
+ "category": "education",
710
+ "source": "inferred_plausible",
711
+ "confidence": "medium",
712
+ "usage_guidance": "use_carefully",
713
+ "example_bullet": ""
714
+ },
715
+ {
716
+ "term": "computer science",
717
+ "category": "education",
718
+ "source": "inferred_plausible",
719
+ "confidence": "medium",
720
+ "usage_guidance": "use_carefully",
721
+ "example_bullet": ""
722
+ },
723
+ {
724
+ "term": "3+ years",
725
+ "category": "seniority",
726
+ "source": "inferred_plausible",
727
+ "confidence": "medium",
728
+ "usage_guidance": "use_carefully",
729
+ "example_bullet": ""
730
+ },
731
+ {
732
+ "term": "microservices",
733
+ "category": "hard_skill",
734
+ "source": "inferred_plausible",
735
+ "confidence": "medium",
736
+ "usage_guidance": "use_carefully",
737
+ "example_bullet": ""
738
+ },
739
+ {
740
+ "term": "architecture",
741
+ "category": "hard_skill",
742
+ "source": "inferred_plausible",
743
+ "confidence": "medium",
744
+ "usage_guidance": "use_carefully",
745
+ "example_bullet": ""
746
+ },
747
+ {
748
+ "term": "system design",
749
+ "category": "hard_skill",
750
+ "source": "risky_review",
751
+ "confidence": "low",
752
+ "usage_guidance": "ask_user",
753
+ "example_bullet": ""
754
+ },
755
+ {
756
+ "term": "ci/cd",
757
+ "category": "hard_skill",
758
+ "source": "inferred_plausible",
759
+ "confidence": "medium",
760
+ "usage_guidance": "use_carefully",
761
+ "example_bullet": ""
762
+ },
763
+ {
764
+ "term": "graphql",
765
+ "category": "hard_skill",
766
+ "source": "inferred_plausible",
767
+ "confidence": "medium",
768
+ "usage_guidance": "use_carefully",
769
+ "example_bullet": ""
770
+ },
771
+ {
772
+ "term": "apis",
773
+ "category": "hard_skill",
774
+ "source": "inferred_plausible",
775
+ "confidence": "medium",
776
+ "usage_guidance": "use_carefully",
777
+ "example_bullet": ""
778
+ },
779
+ {
780
+ "term": "services",
781
+ "category": "hard_skill",
782
+ "source": "inferred_plausible",
783
+ "confidence": "medium",
784
+ "usage_guidance": "use_carefully",
785
+ "example_bullet": ""
786
+ },
787
+ {
788
+ "term": "pipelines",
789
+ "category": "hard_skill",
790
+ "source": "resume_original",
791
+ "confidence": "high",
792
+ "usage_guidance": "safe_to_use",
793
+ "example_bullet": ""
794
+ },
795
+ {
796
+ "term": "database",
797
+ "category": "hard_skill",
798
+ "source": "inferred_plausible",
799
+ "confidence": "medium",
800
+ "usage_guidance": "use_carefully",
801
+ "example_bullet": ""
802
+ },
803
+ {
804
+ "term": "latency",
805
+ "category": "hard_skill",
806
+ "source": "inferred_plausible",
807
+ "confidence": "medium",
808
+ "usage_guidance": "use_carefully",
809
+ "example_bullet": ""
810
+ },
811
+ {
812
+ "term": "optimization",
813
+ "category": "hard_skill",
814
+ "source": "resume_original",
815
+ "confidence": "high",
816
+ "usage_guidance": "safe_to_use",
817
+ "example_bullet": ""
818
+ },
819
+ {
820
+ "term": "backend software",
821
+ "category": "hard_skill",
822
+ "source": "inferred_plausible",
823
+ "confidence": "medium",
824
+ "usage_guidance": "use_carefully",
825
+ "example_bullet": ""
826
+ },
827
+ {
828
+ "term": "software engineer",
829
+ "category": "hard_skill",
830
+ "source": "inferred_plausible",
831
+ "confidence": "medium",
832
+ "usage_guidance": "use_carefully",
833
+ "example_bullet": ""
834
+ },
835
+ {
836
+ "term": "master's",
837
+ "category": "education",
838
+ "source": "inferred_plausible",
839
+ "confidence": "medium",
840
+ "usage_guidance": "use_carefully",
841
+ "example_bullet": ""
842
+ },
843
+ {
844
+ "term": "8+ years",
845
+ "category": "seniority",
846
+ "source": "blocked",
847
+ "confidence": "low",
848
+ "usage_guidance": "blocked",
849
+ "example_bullet": ""
850
+ },
851
+ {
852
+ "term": "senior",
853
+ "category": "seniority",
854
+ "source": "inferred_plausible",
855
+ "confidence": "medium",
856
+ "usage_guidance": "use_carefully",
857
+ "example_bullet": ""
858
+ },
859
+ {
860
+ "term": "platform",
861
+ "category": "hard_skill",
862
+ "source": "resume_original",
863
+ "confidence": "high",
864
+ "usage_guidance": "safe_to_use",
865
+ "example_bullet": ""
866
+ },
867
+ {
868
+ "term": "product strategy",
869
+ "category": "responsibility",
870
+ "source": "resume_original",
871
+ "confidence": "high",
872
+ "usage_guidance": "safe_to_use",
873
+ "example_bullet": ""
874
+ },
875
+ {
876
+ "term": "director",
877
+ "category": "seniority",
878
+ "source": "blocked",
879
+ "confidence": "low",
880
+ "usage_guidance": "blocked",
881
+ "example_bullet": ""
882
+ },
883
+ {
884
+ "term": "5+ years",
885
+ "category": "seniority",
886
+ "source": "resume_original",
887
+ "confidence": "high",
888
+ "usage_guidance": "safe_to_use",
889
+ "example_bullet": ""
890
+ },
891
+ {
892
+ "term": "vp",
893
+ "category": "seniority",
894
+ "source": "blocked",
895
+ "confidence": "low",
896
+ "usage_guidance": "blocked",
897
+ "example_bullet": ""
898
+ },
899
+ {
900
+ "term": "12+ years",
901
+ "category": "seniority",
902
+ "source": "blocked",
903
+ "confidence": "low",
904
+ "usage_guidance": "blocked",
905
+ "example_bullet": ""
906
+ },
907
+ {
908
+ "term": "customer empathy",
909
+ "category": "hard_skill",
910
+ "source": "inferred_plausible",
911
+ "confidence": "medium",
912
+ "usage_guidance": "use_carefully",
913
+ "example_bullet": ""
914
+ },
915
+ {
916
+ "term": "outcomes",
917
+ "category": "hard_skill",
918
+ "source": "resume_original",
919
+ "confidence": "high",
920
+ "usage_guidance": "safe_to_use",
921
+ "example_bullet": ""
922
+ },
923
+ {
924
+ "term": "funnel",
925
+ "category": "hard_skill",
926
+ "source": "resume_original",
927
+ "confidence": "high",
928
+ "usage_guidance": "safe_to_use",
929
+ "example_bullet": ""
930
+ },
931
+ {
932
+ "term": "analysis",
933
+ "category": "hard_skill",
934
+ "source": "inferred_plausible",
935
+ "confidence": "medium",
936
+ "usage_guidance": "use_carefully",
937
+ "example_bullet": ""
938
+ },
939
+ {
940
+ "term": "competitive benchmarking",
941
+ "category": "hard_skill",
942
+ "source": "inferred_plausible",
943
+ "confidence": "medium",
944
+ "usage_guidance": "use_carefully",
945
+ "example_bullet": ""
946
+ },
947
+ {
948
+ "term": "acceptance criteria",
949
+ "category": "hard_skill",
950
+ "source": "resume_original",
951
+ "confidence": "high",
952
+ "usage_guidance": "safe_to_use",
953
+ "example_bullet": ""
954
+ },
955
+ {
956
+ "term": "usability testing",
957
+ "category": "hard_skill",
958
+ "source": "inferred_plausible",
959
+ "confidence": "medium",
960
+ "usage_guidance": "use_carefully",
961
+ "example_bullet": ""
962
+ },
963
+ {
964
+ "term": "mlops",
965
+ "category": "hard_skill",
966
+ "source": "inferred_plausible",
967
+ "confidence": "medium",
968
+ "usage_guidance": "use_carefully",
969
+ "example_bullet": ""
970
+ },
971
+ {
972
+ "term": "growth",
973
+ "category": "hard_skill",
974
+ "source": "resume_original",
975
+ "confidence": "high",
976
+ "usage_guidance": "safe_to_use",
977
+ "example_bullet": ""
978
+ },
979
+ {
980
+ "term": "backlog",
981
+ "category": "hard_skill",
982
+ "source": "inferred_plausible",
983
+ "confidence": "medium",
984
+ "usage_guidance": "use_carefully",
985
+ "example_bullet": ""
986
+ },
987
+ {
988
+ "term": "operations",
989
+ "category": "hard_skill",
990
+ "source": "inferred_plausible",
991
+ "confidence": "medium",
992
+ "usage_guidance": "use_carefully",
993
+ "example_bullet": ""
994
+ },
995
+ {
996
+ "term": "hypothesis",
997
+ "category": "hard_skill",
998
+ "source": "inferred_plausible",
999
+ "confidence": "medium",
1000
+ "usage_guidance": "use_carefully",
1001
+ "example_bullet": ""
1002
+ },
1003
+ {
1004
+ "term": "governance",
1005
+ "category": "hard_skill",
1006
+ "source": "inferred_plausible",
1007
+ "confidence": "medium",
1008
+ "usage_guidance": "use_carefully",
1009
+ "example_bullet": ""
1010
+ },
1011
+ {
1012
+ "term": "collaborate",
1013
+ "category": "hard_skill",
1014
+ "source": "inferred_plausible",
1015
+ "confidence": "medium",
1016
+ "usage_guidance": "use_carefully",
1017
+ "example_bullet": ""
1018
+ },
1019
+ {
1020
+ "term": "system",
1021
+ "category": "hard_skill",
1022
+ "source": "resume_original",
1023
+ "confidence": "high",
1024
+ "usage_guidance": "safe_to_use",
1025
+ "example_bullet": ""
1026
+ },
1027
+ {
1028
+ "term": "cross-functional collaboration",
1029
+ "category": "hard_skill",
1030
+ "source": "resume_original",
1031
+ "confidence": "high",
1032
+ "usage_guidance": "safe_to_use",
1033
+ "example_bullet": ""
1034
+ },
1035
+ {
1036
+ "term": "experiments",
1037
+ "category": "hard_skill",
1038
+ "source": "resume_original",
1039
+ "confidence": "high",
1040
+ "usage_guidance": "safe_to_use",
1041
+ "example_bullet": ""
1042
+ },
1043
+ {
1044
+ "term": "iterative",
1045
+ "category": "hard_skill",
1046
+ "source": "inferred_plausible",
1047
+ "confidence": "medium",
1048
+ "usage_guidance": "use_carefully",
1049
+ "example_bullet": ""
1050
+ },
1051
+ {
1052
+ "term": "product analytics",
1053
+ "category": "hard_skill",
1054
+ "source": "inferred_plausible",
1055
+ "confidence": "medium",
1056
+ "usage_guidance": "use_carefully",
1057
+ "example_bullet": ""
1058
+ },
1059
+ {
1060
+ "term": "sprint planning",
1061
+ "category": "responsibility",
1062
+ "source": "inferred_plausible",
1063
+ "confidence": "medium",
1064
+ "usage_guidance": "use_carefully",
1065
+ "example_bullet": ""
1066
+ },
1067
+ {
1068
+ "term": "user stories",
1069
+ "category": "responsibility",
1070
+ "source": "resume_original",
1071
+ "confidence": "high",
1072
+ "usage_guidance": "safe_to_use",
1073
+ "example_bullet": ""
1074
+ },
1075
+ {
1076
+ "term": "banking",
1077
+ "category": "domain",
1078
+ "source": "inferred_plausible",
1079
+ "confidence": "medium",
1080
+ "usage_guidance": "use_carefully",
1081
+ "example_bullet": ""
1082
+ },
1083
+ {
1084
+ "term": "problem solving",
1085
+ "category": "soft_skill",
1086
+ "source": "inferred_plausible",
1087
+ "confidence": "medium",
1088
+ "usage_guidance": "use_carefully",
1089
+ "example_bullet": ""
1090
+ },
1091
+ {
1092
+ "term": "6 years",
1093
+ "category": "seniority",
1094
+ "source": "inferred_plausible",
1095
+ "confidence": "medium",
1096
+ "usage_guidance": "use_carefully",
1097
+ "example_bullet": ""
1098
+ },
1099
+ {
1100
+ "term": "artificial intelligence",
1101
+ "category": "hard_skill",
1102
+ "source": "inferred_plausible",
1103
+ "confidence": "medium",
1104
+ "usage_guidance": "use_carefully",
1105
+ "example_bullet": ""
1106
+ },
1107
+ {
1108
+ "term": "foundation models",
1109
+ "category": "hard_skill",
1110
+ "source": "inferred_plausible",
1111
+ "confidence": "medium",
1112
+ "usage_guidance": "use_carefully",
1113
+ "example_bullet": ""
1114
+ },
1115
+ {
1116
+ "term": "machine learning",
1117
+ "category": "hard_skill",
1118
+ "source": "resume_original",
1119
+ "confidence": "high",
1120
+ "usage_guidance": "safe_to_use",
1121
+ "example_bullet": ""
1122
+ },
1123
+ {
1124
+ "term": "model validation",
1125
+ "category": "hard_skill",
1126
+ "source": "inferred_plausible",
1127
+ "confidence": "medium",
1128
+ "usage_guidance": "use_carefully",
1129
+ "example_bullet": ""
1130
+ },
1131
+ {
1132
+ "term": "generative ai",
1133
+ "category": "hard_skill",
1134
+ "source": "resume_original",
1135
+ "confidence": "high",
1136
+ "usage_guidance": "safe_to_use",
1137
+ "example_bullet": ""
1138
+ },
1139
+ {
1140
+ "term": "data science",
1141
+ "category": "hard_skill",
1142
+ "source": "inferred_plausible",
1143
+ "confidence": "medium",
1144
+ "usage_guidance": "use_carefully",
1145
+ "example_bullet": ""
1146
+ },
1147
+ {
1148
+ "term": "experimentation",
1149
+ "category": "hard_skill",
1150
+ "source": "resume_original",
1151
+ "confidence": "high",
1152
+ "usage_guidance": "safe_to_use",
1153
+ "example_bullet": ""
1154
+ },
1155
+ {
1156
+ "term": "support",
1157
+ "category": "hard_skill",
1158
+ "source": "inferred_plausible",
1159
+ "confidence": "medium",
1160
+ "usage_guidance": "use_carefully",
1161
+ "example_bullet": ""
1162
+ },
1163
+ {
1164
+ "term": "infrastructure",
1165
+ "category": "hard_skill",
1166
+ "source": "inferred_plausible",
1167
+ "confidence": "medium",
1168
+ "usage_guidance": "use_carefully",
1169
+ "example_bullet": ""
1170
+ },
1171
+ {
1172
+ "term": "prototypes",
1173
+ "category": "hard_skill",
1174
+ "source": "inferred_plausible",
1175
+ "confidence": "medium",
1176
+ "usage_guidance": "use_carefully",
1177
+ "example_bullet": ""
1178
+ },
1179
+ {
1180
+ "term": "applications",
1181
+ "category": "hard_skill",
1182
+ "source": "resume_original",
1183
+ "confidence": "high",
1184
+ "usage_guidance": "safe_to_use",
1185
+ "example_bullet": ""
1186
+ },
1187
+ {
1188
+ "term": "initiatives",
1189
+ "category": "hard_skill",
1190
+ "source": "inferred_plausible",
1191
+ "confidence": "medium",
1192
+ "usage_guidance": "use_carefully",
1193
+ "example_bullet": ""
1194
+ },
1195
+ {
1196
+ "term": "research",
1197
+ "category": "hard_skill",
1198
+ "source": "resume_original",
1199
+ "confidence": "high",
1200
+ "usage_guidance": "safe_to_use",
1201
+ "example_bullet": ""
1202
+ },
1203
+ {
1204
+ "term": "5 to 9",
1205
+ "category": "hard_skill",
1206
+ "source": "inferred_plausible",
1207
+ "confidence": "medium",
1208
+ "usage_guidance": "use_carefully",
1209
+ "example_bullet": ""
1210
+ },
1211
+ {
1212
+ "term": "9 years",
1213
+ "category": "seniority",
1214
+ "source": "blocked",
1215
+ "confidence": "low",
1216
+ "usage_guidance": "blocked",
1217
+ "example_bullet": ""
1218
+ },
1219
+ {
1220
+ "term": "4+ years",
1221
+ "category": "seniority",
1222
+ "source": "inferred_plausible",
1223
+ "confidence": "medium",
1224
+ "usage_guidance": "use_carefully",
1225
+ "example_bullet": ""
1226
+ },
1227
+ {
1228
+ "term": "user testing",
1229
+ "category": "hard_skill",
1230
+ "source": "inferred_plausible",
1231
+ "confidence": "medium",
1232
+ "usage_guidance": "use_carefully",
1233
+ "example_bullet": ""
1234
+ },
1235
+ {
1236
+ "term": "users",
1237
+ "category": "hard_skill",
1238
+ "source": "resume_original",
1239
+ "confidence": "high",
1240
+ "usage_guidance": "safe_to_use",
1241
+ "example_bullet": ""
1242
+ },
1243
+ {
1244
+ "term": "technology",
1245
+ "category": "hard_skill",
1246
+ "source": "resume_original",
1247
+ "confidence": "high",
1248
+ "usage_guidance": "safe_to_use",
1249
+ "example_bullet": ""
1250
+ },
1251
+ {
1252
+ "term": "influence",
1253
+ "category": "hard_skill",
1254
+ "source": "inferred_plausible",
1255
+ "confidence": "medium",
1256
+ "usage_guidance": "use_carefully",
1257
+ "example_bullet": ""
1258
+ },
1259
+ {
1260
+ "term": "business objectives",
1261
+ "category": "hard_skill",
1262
+ "source": "inferred_plausible",
1263
+ "confidence": "medium",
1264
+ "usage_guidance": "use_carefully",
1265
+ "example_bullet": ""
1266
+ },
1267
+ {
1268
+ "term": "administration",
1269
+ "category": "hard_skill",
1270
+ "source": "inferred_plausible",
1271
+ "confidence": "medium",
1272
+ "usage_guidance": "use_carefully",
1273
+ "example_bullet": ""
1274
+ },
1275
+ {
1276
+ "term": "quantitative",
1277
+ "category": "hard_skill",
1278
+ "source": "inferred_plausible",
1279
+ "confidence": "medium",
1280
+ "usage_guidance": "use_carefully",
1281
+ "example_bullet": ""
1282
+ },
1283
+ {
1284
+ "term": "engineering design",
1285
+ "category": "hard_skill",
1286
+ "source": "inferred_plausible",
1287
+ "confidence": "medium",
1288
+ "usage_guidance": "use_carefully",
1289
+ "example_bullet": ""
1290
+ },
1291
+ {
1292
+ "term": "design data",
1293
+ "category": "hard_skill",
1294
+ "source": "inferred_plausible",
1295
+ "confidence": "medium",
1296
+ "usage_guidance": "use_carefully",
1297
+ "example_bullet": ""
1298
+ },
1299
+ {
1300
+ "term": "insurance",
1301
+ "category": "domain",
1302
+ "source": "inferred_plausible",
1303
+ "confidence": "medium",
1304
+ "usage_guidance": "use_carefully",
1305
+ "example_bullet": ""
1306
+ },
1307
+ {
1308
+ "term": "automation",
1309
+ "category": "hard_skill",
1310
+ "source": "resume_original",
1311
+ "confidence": "high",
1312
+ "usage_guidance": "safe_to_use",
1313
+ "example_bullet": ""
1314
+ },
1315
+ {
1316
+ "term": "partners",
1317
+ "category": "hard_skill",
1318
+ "source": "resume_original",
1319
+ "confidence": "high",
1320
+ "usage_guidance": "safe_to_use",
1321
+ "example_bullet": ""
1322
+ },
1323
+ {
1324
+ "term": "processes",
1325
+ "category": "hard_skill",
1326
+ "source": "resume_original",
1327
+ "confidence": "high",
1328
+ "usage_guidance": "safe_to_use",
1329
+ "example_bullet": ""
1330
+ },
1331
+ {
1332
+ "term": "monitoring",
1333
+ "category": "hard_skill",
1334
+ "source": "resume_original",
1335
+ "confidence": "high",
1336
+ "usage_guidance": "safe_to_use",
1337
+ "example_bullet": ""
1338
+ },
1339
+ {
1340
+ "term": "process",
1341
+ "category": "hard_skill",
1342
+ "source": "resume_original",
1343
+ "confidence": "high",
1344
+ "usage_guidance": "safe_to_use",
1345
+ "example_bullet": ""
1346
+ },
1347
+ {
1348
+ "term": "reporting",
1349
+ "category": "hard_skill",
1350
+ "source": "resume_original",
1351
+ "confidence": "high",
1352
+ "usage_guidance": "safe_to_use",
1353
+ "example_bullet": ""
1354
+ },
1355
+ {
1356
+ "term": "platforms",
1357
+ "category": "hard_skill",
1358
+ "source": "resume_original",
1359
+ "confidence": "high",
1360
+ "usage_guidance": "safe_to_use",
1361
+ "example_bullet": ""
1362
+ },
1363
+ {
1364
+ "term": "workflows",
1365
+ "category": "hard_skill",
1366
+ "source": "inferred_plausible",
1367
+ "confidence": "medium",
1368
+ "usage_guidance": "use_carefully",
1369
+ "example_bullet": ""
1370
+ },
1371
+ {
1372
+ "term": "fintech e-commerce",
1373
+ "category": "hard_skill",
1374
+ "source": "inferred_plausible",
1375
+ "confidence": "medium",
1376
+ "usage_guidance": "use_carefully",
1377
+ "example_bullet": ""
1378
+ },
1379
+ {
1380
+ "term": "sql",
1381
+ "category": "tool",
1382
+ "source": "inferred_plausible",
1383
+ "confidence": "medium",
1384
+ "usage_guidance": "use_carefully",
1385
+ "example_bullet": ""
1386
+ },
1387
+ {
1388
+ "term": "excel",
1389
+ "category": "tool",
1390
+ "source": "inferred_plausible",
1391
+ "confidence": "medium",
1392
+ "usage_guidance": "use_carefully",
1393
+ "example_bullet": ""
1394
+ },
1395
+ {
1396
+ "term": "payments",
1397
+ "category": "domain",
1398
+ "source": "resume_original",
1399
+ "confidence": "high",
1400
+ "usage_guidance": "safe_to_use",
1401
+ "example_bullet": ""
1402
+ },
1403
+ {
1404
+ "term": "4 years",
1405
+ "category": "seniority",
1406
+ "source": "inferred_plausible",
1407
+ "confidence": "medium",
1408
+ "usage_guidance": "use_carefully",
1409
+ "example_bullet": ""
1410
+ },
1411
+ {
1412
+ "term": "3 years",
1413
+ "category": "seniority",
1414
+ "source": "inferred_plausible",
1415
+ "confidence": "medium",
1416
+ "usage_guidance": "use_carefully",
1417
+ "example_bullet": ""
1418
+ },
1419
+ {
1420
+ "term": "associate",
1421
+ "category": "seniority",
1422
+ "source": "inferred_plausible",
1423
+ "confidence": "medium",
1424
+ "usage_guidance": "use_carefully",
1425
+ "example_bullet": ""
1426
+ },
1427
+ {
1428
+ "term": "functional specification",
1429
+ "category": "hard_skill",
1430
+ "source": "inferred_plausible",
1431
+ "confidence": "medium",
1432
+ "usage_guidance": "use_carefully",
1433
+ "example_bullet": ""
1434
+ },
1435
+ {
1436
+ "term": "requirements elicitation",
1437
+ "category": "hard_skill",
1438
+ "source": "inferred_plausible",
1439
+ "confidence": "medium",
1440
+ "usage_guidance": "use_carefully",
1441
+ "example_bullet": ""
1442
+ },
1443
+ {
1444
+ "term": "test plans",
1445
+ "category": "hard_skill",
1446
+ "source": "inferred_plausible",
1447
+ "confidence": "medium",
1448
+ "usage_guidance": "use_carefully",
1449
+ "example_bullet": ""
1450
+ },
1451
+ {
1452
+ "term": "uat",
1453
+ "category": "hard_skill",
1454
+ "source": "inferred_plausible",
1455
+ "confidence": "medium",
1456
+ "usage_guidance": "use_carefully",
1457
+ "example_bullet": ""
1458
+ },
1459
+ {
1460
+ "term": "documentation",
1461
+ "category": "hard_skill",
1462
+ "source": "resume_original",
1463
+ "confidence": "high",
1464
+ "usage_guidance": "safe_to_use",
1465
+ "example_bullet": ""
1466
+ },
1467
+ {
1468
+ "term": "fsd",
1469
+ "category": "hard_skill",
1470
+ "source": "inferred_plausible",
1471
+ "confidence": "medium",
1472
+ "usage_guidance": "use_carefully",
1473
+ "example_bullet": ""
1474
+ },
1475
+ {
1476
+ "term": "scope",
1477
+ "category": "hard_skill",
1478
+ "source": "inferred_plausible",
1479
+ "confidence": "medium",
1480
+ "usage_guidance": "use_carefully",
1481
+ "example_bullet": ""
1482
+ },
1483
+ {
1484
+ "term": "projects",
1485
+ "category": "hard_skill",
1486
+ "source": "resume_original",
1487
+ "confidence": "high",
1488
+ "usage_guidance": "safe_to_use",
1489
+ "example_bullet": ""
1490
+ },
1491
+ {
1492
+ "term": "implementation",
1493
+ "category": "hard_skill",
1494
+ "source": "resume_original",
1495
+ "confidence": "high",
1496
+ "usage_guidance": "safe_to_use",
1497
+ "example_bullet": ""
1498
+ },
1499
+ {
1500
+ "term": "team",
1501
+ "category": "hard_skill",
1502
+ "source": "resume_original",
1503
+ "confidence": "high",
1504
+ "usage_guidance": "safe_to_use",
1505
+ "example_bullet": ""
1506
+ },
1507
+ {
1508
+ "term": "timelines",
1509
+ "category": "hard_skill",
1510
+ "source": "inferred_plausible",
1511
+ "confidence": "medium",
1512
+ "usage_guidance": "use_carefully",
1513
+ "example_bullet": ""
1514
+ },
1515
+ {
1516
+ "term": "coordination",
1517
+ "category": "hard_skill",
1518
+ "source": "inferred_plausible",
1519
+ "confidence": "medium",
1520
+ "usage_guidance": "use_carefully",
1521
+ "example_bullet": ""
1522
+ },
1523
+ {
1524
+ "term": "elicitation design",
1525
+ "category": "hard_skill",
1526
+ "source": "inferred_plausible",
1527
+ "confidence": "medium",
1528
+ "usage_guidance": "use_carefully",
1529
+ "example_bullet": ""
1530
+ },
1531
+ {
1532
+ "term": "user acceptance testing",
1533
+ "category": "responsibility",
1534
+ "source": "resume_original",
1535
+ "confidence": "high",
1536
+ "usage_guidance": "safe_to_use",
1537
+ "example_bullet": ""
1538
+ },
1539
+ {
1540
+ "term": "go-to-market strategy",
1541
+ "category": "hard_skill",
1542
+ "source": "inferred_plausible",
1543
+ "confidence": "medium",
1544
+ "usage_guidance": "use_carefully",
1545
+ "example_bullet": ""
1546
+ },
1547
+ {
1548
+ "term": "prds",
1549
+ "category": "hard_skill",
1550
+ "source": "inferred_plausible",
1551
+ "confidence": "medium",
1552
+ "usage_guidance": "use_carefully",
1553
+ "example_bullet": ""
1554
+ },
1555
+ {
1556
+ "term": "ceremonies",
1557
+ "category": "hard_skill",
1558
+ "source": "inferred_plausible",
1559
+ "confidence": "medium",
1560
+ "usage_guidance": "use_carefully",
1561
+ "example_bullet": ""
1562
+ },
1563
+ {
1564
+ "term": "adoption",
1565
+ "category": "hard_skill",
1566
+ "source": "inferred_plausible",
1567
+ "confidence": "medium",
1568
+ "usage_guidance": "use_carefully",
1569
+ "example_bullet": ""
1570
+ },
1571
+ {
1572
+ "term": "okr",
1573
+ "category": "hard_skill",
1574
+ "source": "inferred_plausible",
1575
+ "confidence": "medium",
1576
+ "usage_guidance": "use_carefully",
1577
+ "example_bullet": ""
1578
+ },
1579
+ {
1580
+ "term": "heap",
1581
+ "category": "hard_skill",
1582
+ "source": "inferred_plausible",
1583
+ "confidence": "medium",
1584
+ "usage_guidance": "use_carefully",
1585
+ "example_bullet": ""
1586
+ },
1587
+ {
1588
+ "term": "retention",
1589
+ "category": "hard_skill",
1590
+ "source": "resume_original",
1591
+ "confidence": "high",
1592
+ "usage_guidance": "safe_to_use",
1593
+ "example_bullet": ""
1594
+ },
1595
+ {
1596
+ "term": "activation",
1597
+ "category": "hard_skill",
1598
+ "source": "inferred_plausible",
1599
+ "confidence": "medium",
1600
+ "usage_guidance": "use_carefully",
1601
+ "example_bullet": ""
1602
+ },
1603
+ {
1604
+ "term": "recommendation",
1605
+ "category": "hard_skill",
1606
+ "source": "inferred_plausible",
1607
+ "confidence": "medium",
1608
+ "usage_guidance": "use_carefully",
1609
+ "example_bullet": ""
1610
+ },
1611
+ {
1612
+ "term": "onboarding",
1613
+ "category": "hard_skill",
1614
+ "source": "resume_original",
1615
+ "confidence": "high",
1616
+ "usage_guidance": "safe_to_use",
1617
+ "example_bullet": ""
1618
+ },
1619
+ {
1620
+ "term": "personalization",
1621
+ "category": "hard_skill",
1622
+ "source": "inferred_plausible",
1623
+ "confidence": "medium",
1624
+ "usage_guidance": "use_carefully",
1625
+ "example_bullet": ""
1626
+ },
1627
+ {
1628
+ "term": "frameworks",
1629
+ "category": "hard_skill",
1630
+ "source": "inferred_plausible",
1631
+ "confidence": "medium",
1632
+ "usage_guidance": "use_carefully",
1633
+ "example_bullet": ""
1634
+ },
1635
+ {
1636
+ "term": "nps",
1637
+ "category": "hard_skill",
1638
+ "source": "inferred_plausible",
1639
+ "confidence": "medium",
1640
+ "usage_guidance": "use_carefully",
1641
+ "example_bullet": ""
1642
+ },
1643
+ {
1644
+ "term": "dashboards",
1645
+ "category": "hard_skill",
1646
+ "source": "resume_original",
1647
+ "confidence": "high",
1648
+ "usage_guidance": "safe_to_use",
1649
+ "example_bullet": ""
1650
+ },
1651
+ {
1652
+ "term": "a/b tests",
1653
+ "category": "hard_skill",
1654
+ "source": "resume_original",
1655
+ "confidence": "high",
1656
+ "usage_guidance": "safe_to_use",
1657
+ "example_bullet": ""
1658
+ },
1659
+ {
1660
+ "term": "looker metabase",
1661
+ "category": "hard_skill",
1662
+ "source": "inferred_plausible",
1663
+ "confidence": "medium",
1664
+ "usage_guidance": "use_carefully",
1665
+ "example_bullet": ""
1666
+ },
1667
+ {
1668
+ "term": "amplitude",
1669
+ "category": "tool",
1670
+ "source": "inferred_plausible",
1671
+ "confidence": "medium",
1672
+ "usage_guidance": "use_carefully",
1673
+ "example_bullet": ""
1674
+ },
1675
+ {
1676
+ "term": "mixpanel",
1677
+ "category": "tool",
1678
+ "source": "inferred_plausible",
1679
+ "confidence": "medium",
1680
+ "usage_guidance": "use_carefully",
1681
+ "example_bullet": ""
1682
+ },
1683
+ {
1684
+ "term": "b2c",
1685
+ "category": "domain",
1686
+ "source": "inferred_plausible",
1687
+ "confidence": "medium",
1688
+ "usage_guidance": "use_carefully",
1689
+ "example_bullet": ""
1690
+ },
1691
+ {
1692
+ "term": "marketplace",
1693
+ "category": "domain",
1694
+ "source": "inferred_plausible",
1695
+ "confidence": "medium",
1696
+ "usage_guidance": "use_carefully",
1697
+ "example_bullet": ""
1698
+ },
1699
+ {
1700
+ "term": "program",
1701
+ "category": "hard_skill",
1702
+ "source": "inferred_plausible",
1703
+ "confidence": "medium",
1704
+ "usage_guidance": "use_carefully",
1705
+ "example_bullet": ""
1706
+ },
1707
+ {
1708
+ "term": "product discovery",
1709
+ "category": "responsibility",
1710
+ "source": "resume_original",
1711
+ "confidence": "high",
1712
+ "usage_guidance": "safe_to_use",
1713
+ "example_bullet": ""
1714
+ },
1715
+ {
1716
+ "term": "user research",
1717
+ "category": "responsibility",
1718
+ "source": "resume_original",
1719
+ "confidence": "high",
1720
+ "usage_guidance": "safe_to_use",
1721
+ "example_bullet": ""
1722
+ },
1723
+ {
1724
+ "term": "a/b testing",
1725
+ "category": "responsibility",
1726
+ "source": "resume_original",
1727
+ "confidence": "high",
1728
+ "usage_guidance": "safe_to_use",
1729
+ "example_bullet": ""
1730
+ }
1731
+ ]
1732
+ }
data/resume/_parsed.json ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_cache_key": "v2_1781861629562490500_94919",
3
+ "resume": {
4
+ "name": "Saiteja Tirunagari",
5
+ "contact": {
6
+ "phone": "+91 9988996588",
7
+ "email": "saitejatirunagari@gmail.com",
8
+ "linkedin": "https://linkedin.com/in/saitejatirunagari",
9
+ "website": "",
10
+ "location": "AI-First Product Manager · 5+ Years · Hyderabad, India"
11
+ },
12
+ "summary": "AI-first Product Manager with 5+ years driving 0→1 product development in fast-paced EdTech startups. Proven record building digital acquisition funnels, automation pipelines, and AI-powered tools that have processed 140,000+ users and contributed to 2× revenue growth. Deep expertise in conversational AI, OCR automation, funnel optimization, A/B experimentation, and cross- functional delivery. Combines data-driven decision-making with user-centric design to ship measurable outcomes—from +35 pp payment conversion lifts to 3.8× report engagement jumps.",
13
+ "skills": [],
14
+ "roles": [
15
+ {
16
+ "title": "Internal Product Manager",
17
+ "company": "NxtWave Disruptive Technologies Pvt. Ltd.",
18
+ "location": "Hyderabad, India",
19
+ "dates": "Jan 2023 – Present",
20
+ "bullets": [
21
+ "Led end-to-end revamp of NIAT Application Portal—a unified digital funnel covering landing pages → OTP login → personal details → payment → slot booking → exam → report → sales flow—integrated with CRM, WebEngage, and payment systems",
22
+ "Scaled to 141,269 OTP-verified leads; achieved 97% personal-details completion and 95% exam-attendance rate across 23,983 attendees, with 210 final enrollments",
23
+ "Maintained ₹1,120+ Cr annual pipeline value across 8,000+ processed applications",
24
+ "Introduced contextual loaders and UX refinements, eliminating idle wait perception and reducing early-stage drop-offs",
25
+ "Implemented coupon-based urgency logic in the payment flow, lifting 0–60 min payment completion from 27.37% → 63.24% (+35.87 pp) for coupon users",
26
+ "Improved overall funnel payment conversion by +6.98 pp, accelerating time-to-revenue with near-zero acquisition cost increase",
27
+ "Redesigned the NIAT landing-page AI chatbot into a structured conversion engine with stage-wise decision trees and CRM- integrated nudges aligned to every funnel milestone (lead → application → payment → exam → enrollment)",
28
+ "Generated 6,776 leads and 113 enrollments via chatbot-driven funnel; chatbot independently sourced 1,744 leads and 440 applications"
29
+ ]
30
+ },
31
+ {
32
+ "title": "Asst. Product Success Manager – User Experience",
33
+ "company": "Think & Learn Pvt. Ltd. (BYJU'S)",
34
+ "location": "Bengaluru, India",
35
+ "dates": "Oct 2021 – Dec 2022",
36
+ "bullets": [
37
+ "Managed 20 customer-success specialists covering 40,000 customers; maintained refund rate below 5% and customer satisfaction above 95%",
38
+ "Played 0→1 role in Xplore Experiment and Social Emotional Learning pilot projects alongside product and engineering teams",
39
+ "Sustained 95%+ Monthly Recurring Revenue from existing EMI customers through proactive retention strategies",
40
+ "Designed robust processes and drove adherence across teams to ensure consistent execution and sustainable growth",
41
+ "Created comprehensive customer documentation and educated users on new product capabilities and technical feasibility"
42
+ ]
43
+ },
44
+ {
45
+ "title": "Product Specialist – User Experience",
46
+ "company": "Think & Learn Pvt. Ltd. (BYJU'S)",
47
+ "location": "Bengaluru, India",
48
+ "dates": "Aug 2019 – Sep 2021",
49
+ "bullets": [
50
+ "Increased user retention by 8% by redesigning the onboarding process using UX research and user-centric principles",
51
+ "Conducted extensive UX research and A/B testing to identify pain points and refine features, improving learning-platform engagement",
52
+ "Mentored students throughout their academic journey using multi-channel communication; monitored performance dashboards and shared progress reports with stakeholders"
53
+ ]
54
+ },
55
+ {
56
+ "title": "Founder & CEO",
57
+ "company": "ML Edutech",
58
+ "location": "Hyderabad, India",
59
+ "dates": "Aug 2015 – Jul 2019",
60
+ "bullets": [
61
+ "Launched EdTech app portfolio of 275 apps with 3 million+ cumulative downloads",
62
+ "Drove user acquisition through Google Ads, LinkedIn, and paid social; established strategic partnerships and managed end-to- end P&L",
63
+ "Built a performance-driven culture focused on conversion optimization, data-driven decision-making, and sustainable growth"
64
+ ]
65
+ }
66
+ ],
67
+ "achievements": [
68
+ "141,269 OTP-verified leads processed through rebuilt NIAT Application Portal (2026 cycle)",
69
+ "2× business revenue growth in <9 months via automation and AI-powered funnel optimization",
70
+ "Payment conversion: 27.37% → 63.24% (+35.87 pp) for coupon users · Overall lift +6.98 pp",
71
+ "₹1,120+ Cr annual pipeline managed across 8,000+ applications",
72
+ "8,000+ admissions applications processed; ₹45,600 offline exam revenue from 1,503 paid users"
73
+ ],
74
+ "education": [
75
+ {
76
+ "degree": "Diploma – Product & Brand Management",
77
+ "institution": "IIM Rohtak",
78
+ "dates": "Mar 2023 – Sep 2023"
79
+ },
80
+ {
81
+ "degree": "Diploma in Business Management",
82
+ "institution": "Osmania University, Hyderabad",
83
+ "dates": "Aug 2015 – Jul 2019"
84
+ },
85
+ {
86
+ "degree": "Bachelor of Engineering – Civil Engineering",
87
+ "institution": "JNTU Hyderabad",
88
+ "dates": "Aug 2011 – Sep 2016"
89
+ }
90
+ ]
91
+ }
92
+ }
scripts/accept_v1_live.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LIVE V1 acceptance harness — real model, real JDs, real résumé, real PDFs.
2
+
3
+ Runs the PRODUCTION path (nim_fallback.build_llm -> generate_alignment_safe with
4
+ run_audit) against 3 real job postings + the real candidate résumé, renders the
5
+ real ATS-safe PDF, parses it with two independent parsers (pdftotext + pymupdf),
6
+ and runs the independent post-generation audit. No mocks, no hard-coded criteria.
7
+
8
+ Run: python scripts/accept_v1_live.py
9
+ Outputs PDFs + a JSON evidence file under data/acceptance/out/.
10
+ """
11
+ import json
12
+ import os
13
+ import re
14
+ import sys
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
+
18
+ from src.default_resume import get_default_resume_latex
19
+ from src.nim_fallback import build_llm
20
+ from src.ats_safe import generate_alignment_safe, to_legacy_report
21
+ from src.latex_resume import latex_to_text
22
+
23
+ ACC = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
24
+ "data", "acceptance")
25
+ OUT = os.path.join(ACC, "out")
26
+ os.makedirs(OUT, exist_ok=True)
27
+
28
+ CASES = [
29
+ ("case1_stripe_payments", "Staff Product Manager, Payments", "jd1_stripe_payments.txt"),
30
+ ("case2_stripe_genai", "Staff Product Manager, ML & GenAI", "jd2_stripe_genai.txt"),
31
+ ("case3_growth_marketing", "Growth Marketing Manager, B2B SaaS", "jd3_growth_marketing.txt"),
32
+ ]
33
+
34
+
35
+ def _load(fn):
36
+ raw = open(os.path.join(ACC, fn), encoding="utf-8").read()
37
+ url = ""
38
+ m = re.search(r"SOURCE_URL:\s*(\S+)", raw)
39
+ if m:
40
+ url = m.group(1)
41
+ raw = raw[m.end():].strip()
42
+ return url, raw
43
+
44
+
45
+ def run_case(slug, title, fn, llm, health):
46
+ url, jd = _load(fn)
47
+ resume = get_default_resume_latex()
48
+ safe = generate_alignment_safe(
49
+ resume, jd, company="", job_title=title,
50
+ llm_client=llm, selected_model=getattr(llm, "model", None),
51
+ model_health=health, run_audit=True, out_dir=OUT, compile_pdf=True)
52
+ est = safe.get("internal_alignment_estimate") or {}
53
+ audit = safe.get("independent_audit") or {}
54
+ accept = safe.get("acceptance") or {}
55
+ twop = safe.get("pdf_two_parser") or {}
56
+
57
+ # persist PDF under a stable name
58
+ pdf_out = os.path.join(OUT, f"{slug}.pdf")
59
+ if safe.get("pdf_path") and os.path.exists(safe["pdf_path"]) and safe["pdf_path"] != pdf_out:
60
+ import shutil
61
+ shutil.copy(safe["pdf_path"], pdf_out)
62
+
63
+ evidence = {
64
+ "case": slug, "title": title, "source_url": url,
65
+ "selected_model": getattr(llm, "model", None),
66
+ "status": safe.get("status"), "reason": safe.get("reason"),
67
+ "jd_clean_confidence": safe.get("jd_diagnostics", {}).get("confidence"),
68
+ "calibration": safe.get("calibration"),
69
+ "applied_rewrites": [{"phrase": r["exact_jd_phrase"], "before": r["original_resume_text"],
70
+ "after": r["rewritten_text"], "type": r["change_type"]}
71
+ for r in safe.get("rewrites", []) if r.get("applied")],
72
+ "rejected_rewrites": [{"phrase": r["exact_jd_phrase"], "reason": r["reject_reason"]}
73
+ for r in safe.get("rewrites", []) if not r.get("applied") and r.get("reject_reason")],
74
+ "summary_rewrite": safe.get("summary_rewrite"),
75
+ "gaps": [g["keyword"] for g in safe.get("evidence", {}).get("gaps", [])],
76
+ "score_before": est.get("before"), "score_after": est.get("after"),
77
+ "max_evidence_supported": est.get("max_evidence_supported"),
78
+ "gate_90_passed": est.get("gate_90_passed"),
79
+ "scored_from": est.get("scored_from"),
80
+ "independent_audit": audit,
81
+ "pdf_two_parser": twop,
82
+ "acceptance": accept,
83
+ "pdf_path": pdf_out if os.path.exists(pdf_out) else safe.get("pdf_path"),
84
+ }
85
+ with open(os.path.join(OUT, f"{slug}.json"), "w", encoding="utf-8") as f:
86
+ json.dump(evidence, f, indent=1, ensure_ascii=False)
87
+ return evidence
88
+
89
+
90
+ def main():
91
+ llm, health = build_llm(timeout=30)
92
+ print("SELECTED MODEL:", getattr(llm, "model", None))
93
+ for h in health:
94
+ print(f" {h['requested_model']:<42} {h['status']}")
95
+ if llm is None:
96
+ print("live_model_unavailable — cannot run live acceptance.")
97
+ return
98
+ summary = []
99
+ for slug, title, fn in CASES:
100
+ print(f"\n===== {slug} : {title} =====")
101
+ ev = run_case(slug, title, fn, llm, health)
102
+ print(f" model={ev['selected_model']} status={ev['status']}")
103
+ print(f" before={ev['score_before']} after={ev['score_after']} "
104
+ f"ceiling={ev['max_evidence_supported']} gate90={ev['gate_90_passed']}")
105
+ print(f" applied={len(ev['applied_rewrites'])} gaps={len(ev['gaps'])} "
106
+ f"summary_applied={(ev['summary_rewrite'] or {}).get('applied')}")
107
+ au = ev["independent_audit"]
108
+ print(f" AUDIT: indep_score={au.get('independent_score')} "
109
+ f"mand_missing={au.get('supported_mandatory_missing')} "
110
+ f"crit_missing={au.get('supported_critical_missing')}")
111
+ print(f" 2-parser: present_in_both={ev['pdf_two_parser'].get('present_in_both')}"
112
+ f"/{ev['pdf_two_parser'].get('keywords_checked')} "
113
+ f"missing={ev['pdf_two_parser'].get('missing_or_split')}")
114
+ print(f" ACCEPTANCE: {ev['acceptance']}")
115
+ print(f" PDF: {ev['pdf_path']}")
116
+ summary.append(ev)
117
+ with open(os.path.join(OUT, "SUMMARY.json"), "w", encoding="utf-8") as f:
118
+ json.dump(summary, f, indent=1, ensure_ascii=False)
119
+ print("\n==== DONE. Evidence in data/acceptance/out/ ====")
120
+
121
+
122
+ if __name__ == "__main__":
123
+ main()
src/ats_evaluate.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Independent post-generation keyword-completeness audit.
2
+
3
+ Runs a SECOND, independent extraction from the cleaned JD (a fresh model call,
4
+ not the generator's criterion list) and compares it against the FINAL PDF-parsed
5
+ text. This stops the system from awarding itself a high score when its first
6
+ extractor missed important JD terms — the audit, not the generator, has the last
7
+ word on the reported score.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Dict, List
12
+
13
+ from .keyword_schema import validate_and_repair, calibrate
14
+ from .evidence_gate import map_evidence
15
+ from .ats_score import score_alignment, compute_coverage, _present
16
+
17
+
18
+ def independent_audit(clean_jd: str, original_resume_text: str,
19
+ final_pdf_text: str, llm) -> Dict:
20
+ """Return an independent audit of the final résumé against the JD.
21
+
22
+ `llm` performs a fresh extraction (independent of the generator). Evidence is
23
+ judged against the ORIGINAL résumé (what the candidate genuinely supports);
24
+ presence is judged against the FINAL PDF-parsed text (what an ATS will read).
25
+ """
26
+ audit: Dict = {"ok": False, "reason": "", "independent_criteria": 0}
27
+ try:
28
+ raw = llm.extract_keywords_structured(clean_jd)
29
+ except Exception as e:
30
+ audit["reason"] = f"independent_extraction_failed:{str(e)[:120]}"
31
+ return audit
32
+ valid, _ = validate_and_repair(raw, clean_jd)
33
+ if not valid:
34
+ audit["reason"] = "independent_extraction_empty"
35
+ return audit
36
+ valid = calibrate(valid)
37
+ audit["independent_criteria"] = len(valid)
38
+
39
+ # supported = evidence in the ORIGINAL résumé
40
+ ev = map_evidence(valid, original_resume_text)
41
+ supported = {c.keyword for c in ev.covered}
42
+ low_final = (final_pdf_text or "").lower()
43
+
44
+ def _in_final(c):
45
+ return (_present(low_final, c.get("exact_phrase", ""))
46
+ or _present(low_final, c.get("normalized_concept", "")))
47
+
48
+ mand = [c for c in valid if c.get("requirement_type") == "required"]
49
+ crit = [c for c in valid if (c.get("calibration_weight") or 0) > 0]
50
+
51
+ def _split(items):
52
+ sup = [c for c in items if c.get("normalized_concept") in supported]
53
+ return ([c["exact_phrase"] for c in sup if _in_final(c)],
54
+ [c["exact_phrase"] for c in sup if not _in_final(c)])
55
+
56
+ mand_present, mand_missing = _split(mand)
57
+ crit_present, crit_missing = _split(crit)
58
+ # exact-phrase presence over supported critical
59
+ sup_crit = [c for c in crit if c.get("normalized_concept") in supported]
60
+ exact_present = [c["exact_phrase"] for c in sup_crit
61
+ if _present(low_final, c.get("exact_phrase", ""))]
62
+ exact_missing = [c["exact_phrase"] for c in sup_crit
63
+ if not _present(low_final, c.get("exact_phrase", ""))]
64
+ # concepts covered only semantically (partial evidence, not a clear capability)
65
+ semantic_only = [p.exact_phrase for p in ev.partial]
66
+ # unsupported requirements correctly excluded (gaps not in final)
67
+ unsupported_excluded = [g.keyword for g in ev.gaps
68
+ if not _present(low_final, g.keyword)]
69
+ unsupported_leaked = [g.keyword for g in ev.gaps
70
+ if _present(low_final, g.keyword)]
71
+
72
+ # Score from FINAL PDF text using the INDEPENDENT criteria.
73
+ score = score_alignment(valid, ev.to_dict(), final_pdf_text)
74
+ cov = score["coverage"]
75
+
76
+ audit.update({
77
+ "ok": True, "reason": "audited",
78
+ "supported_mandatory_present": mand_present,
79
+ "supported_mandatory_missing": mand_missing,
80
+ "supported_critical_present": crit_present,
81
+ "supported_critical_missing": crit_missing,
82
+ "important_exact_present": exact_present,
83
+ "important_exact_missing": exact_missing,
84
+ "semantic_only": semantic_only,
85
+ "unsupported_correctly_excluded": unsupported_excluded,
86
+ "unsupported_leaked_into_resume": unsupported_leaked,
87
+ "coverage": cov,
88
+ "independent_score": score["score"],
89
+ "gate_90_passed": score["gate_90_passed"],
90
+ })
91
+ return audit
92
+
93
+
94
+ def acceptance_verdict(audit: Dict, unsupported_insertions: int,
95
+ stuffing_penalty: float, pdf_two_parser: Dict) -> Dict:
96
+ """Final acceptance decision. A score >=90 is only ACCEPTED when the
97
+ independent audit + PDF checks all pass; otherwise the reported score is
98
+ capped and the reasons are listed."""
99
+ reasons = []
100
+ if not audit.get("ok"):
101
+ return {"accepted_score": 0.0, "accepted": False,
102
+ "reasons": [audit.get("reason", "audit_failed")]}
103
+ cov = audit.get("coverage", {})
104
+ if audit.get("supported_mandatory_missing"):
105
+ reasons.append("supported mandatory criteria missing from final PDF")
106
+ if (cov.get("critical_family_coverage") or 0) < 0.90:
107
+ reasons.append("critical family coverage < 90%")
108
+ if (cov.get("critical_exact_phrase_coverage") or 0) < 0.85:
109
+ reasons.append("critical exact-phrase coverage < 85%")
110
+ if unsupported_insertions > 0:
111
+ reasons.append("unsupported insertions present")
112
+ if audit.get("unsupported_leaked_into_resume"):
113
+ reasons.append("unsupported requirement leaked into résumé")
114
+ if stuffing_penalty > 0:
115
+ reasons.append("keyword-stuffing penalty > 0")
116
+ if pdf_two_parser and pdf_two_parser.get("missing_or_split"):
117
+ reasons.append("accepted keyword not present in both parsers")
118
+ score = audit.get("independent_score", 0.0)
119
+ accepted_90 = (score >= 90) and not reasons
120
+ if score >= 90 and reasons:
121
+ score = min(score, 89.0) # cannot claim >=90 unless audit clean
122
+ return {"accepted_score": score, "accepted_ge_90": accepted_90,
123
+ "blocking_reasons": reasons}
124
+
125
+
126
+ if __name__ == "__main__": # ponytail: light self-check with a deterministic stub
127
+ class Stub:
128
+ def extract_keywords_structured(self, jd):
129
+ return [{"exact_phrase": "SQL", "normalized_concept": "sql", "category": "tool",
130
+ "requirement_type": "required", "importance": "high", "source_text": "SQL",
131
+ "semantic_variants": [], "confidence": 0.9, "requires_resume_evidence": True}]
132
+ jd = "Requirements: you must have strong SQL."
133
+ a = independent_audit(jd, "Built SQL dashboards.", "Built SQL dashboards for analytics.", Stub())
134
+ assert a["ok"] and "SQL" in a["supported_mandatory_present"], a
135
+ print("ats_evaluate self-check PASSED:", a["independent_score"], a["gate_90_passed"])
src/ats_safe.py CHANGED
@@ -21,6 +21,7 @@ Greenhouse score.
21
  """
22
  from __future__ import annotations
23
 
 
24
  import re
25
  import tempfile
26
  from typing import Dict, List, Optional
@@ -69,15 +70,19 @@ def _deterministic_report_only_items(clean_jd: str) -> List[dict]:
69
 
70
 
71
  def _resume_section_order(latex_src: str, resume_text: str) -> List[str]:
72
- """Best-effort actual section order for PDF-order validation."""
73
- candidates = ["summary", "experience", "projects", "education", "skills"]
74
- low = resume_text.lower()
75
- present = []
 
76
  for c in candidates:
77
- if re.search(r"(?im)^[^\S\n]*" + re.escape(c) + r"s?[^\S\n]*$", resume_text) \
78
- or c.upper() in latex_src:
79
- present.append(c)
80
- return present or ["experience", "education", "skills"]
 
 
 
81
 
82
 
83
  def generate_alignment_safe(
@@ -88,8 +93,10 @@ def generate_alignment_safe(
88
  job_title: str = "",
89
  llm_client=None,
90
  rewrite_fn=None,
 
91
  selected_model: Optional[str] = None,
92
  model_health: Optional[list] = None,
 
93
  out_dir: Optional[str] = None,
94
  compile_pdf: bool = True,
95
  progress_callback=None,
@@ -165,6 +172,23 @@ def generate_alignment_safe(
165
  raw_items = _deterministic_report_only_items(clean_jd)
166
  report["extraction"]["used_fallback"] = used_fallback
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  # 3. Validate + traceability gate.
169
  _prog("Validating extraction…", 40)
170
  valid, rejected = validate_and_repair(raw_items, clean_jd)
@@ -210,6 +234,25 @@ def generate_alignment_safe(
210
  latex_src, ev_before.rewrite_candidates(), effective_rewrite_fn)
211
  rewrite_records = [r.to_dict() for r in recs]
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  # 6. Compile + PDF-parse (so scoring can use PARSED text, not just LaTeX).
214
  _compile_preserved(report, final_latex, out_dir, job_title, compile_pdf,
215
  compile_latex_to_pdf, _safe_jobname, _prog,
@@ -276,6 +319,26 @@ def generate_alignment_safe(
276
  "mandatory_recall": ev_after.metrics().get("mandatory_recall"),
277
  "scored_from": "parsed_pdf" if report.get("_pdf_text_used") else "latex_text",
278
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  return report
280
 
281
 
@@ -423,7 +486,21 @@ def _compile_preserved(report, latex_src, out_dir, job_title, compile_pdf,
423
  report["compile_log"] = comp.get("log", "")
424
  except Exception as e:
425
  report["compile_log"] = f"compile_error: {e}"
426
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  if report.get("pdf_path"):
429
  from .pdf_validate import validate_pdf
 
21
  """
22
  from __future__ import annotations
23
 
24
+ import os
25
  import re
26
  import tempfile
27
  from typing import Dict, List, Optional
 
70
 
71
 
72
  def _resume_section_order(latex_src: str, resume_text: str) -> List[str]:
73
+ """Actual section order as it appears in the résumé SOURCE (so PDF-order
74
+ validation compares against the résumé's own order, not a fixed assumption)."""
75
+ candidates = ["summary", "experience", "projects", "education", "skills",
76
+ "certifications"]
77
+ found = []
78
  for c in candidates:
79
+ m = re.search(r"\\section\*?\{[^}]*" + re.escape(c) + r"[^}]*\}", latex_src, re.I)
80
+ pos = m.start() if m else (latex_src.lower().find(c.upper().lower())
81
+ if c.upper() in latex_src else -1)
82
+ if pos >= 0:
83
+ found.append((pos, c))
84
+ ordered = [c for _, c in sorted(found)]
85
+ return ordered or ["experience", "education", "skills"]
86
 
87
 
88
  def generate_alignment_safe(
 
93
  job_title: str = "",
94
  llm_client=None,
95
  rewrite_fn=None,
96
+ summary_fn=None,
97
  selected_model: Optional[str] = None,
98
  model_health: Optional[list] = None,
99
+ run_audit: bool = False,
100
  out_dir: Optional[str] = None,
101
  compile_pdf: bool = True,
102
  progress_callback=None,
 
172
  raw_items = _deterministic_report_only_items(clean_jd)
173
  report["extraction"]["used_fallback"] = used_fallback
174
 
175
+ # 2.5. Completeness check — if the live extraction missed obvious source-
176
+ # grounded requirements (named tools, explicit years), retry ONCE with a
177
+ # correction hint before trusting it. (No award from an incomplete list.)
178
+ if not used_fallback and llm_client is not None:
179
+ try:
180
+ from .nim_fallback import extraction_gaps
181
+ _pre_valid, _ = validate_and_repair(raw_items, clean_jd)
182
+ gaps = extraction_gaps(clean_jd, calibrate(_pre_valid))
183
+ if gaps:
184
+ report["extraction"]["completeness_retry"] = gaps
185
+ retried = llm_client.extract_keywords_structured(
186
+ clean_jd, correction_hint=", ".join(gaps))
187
+ if retried:
188
+ raw_items = retried
189
+ except Exception as e:
190
+ report["extraction"]["completeness_error"] = str(e)[:120]
191
+
192
  # 3. Validate + traceability gate.
193
  _prog("Validating extraction…", 40)
194
  valid, rejected = validate_and_repair(raw_items, clean_jd)
 
234
  latex_src, ev_before.rewrite_candidates(), effective_rewrite_fn)
235
  rewrite_records = [r.to_dict() for r in recs]
236
 
237
+ # 5.5. Headline/summary optimization — rewrite the SUMMARY toward the target
238
+ # role using ONLY résumé-corpus facts + top supported phrases (corpus-
239
+ # verified; keeps original on any fabrication signal).
240
+ eff_summary_fn = summary_fn
241
+ if eff_summary_fn is None and llm_client is not None \
242
+ and hasattr(llm_client, "rewrite_summary"):
243
+ eff_summary_fn = llm_client.rewrite_summary
244
+ report["summary_rewrite"] = None
245
+ if eff_summary_fn is not None:
246
+ from .resume_rewrite import optimize_summary
247
+ top_phrases = [c["exact_phrase"] for c in valid
248
+ if (c.get("calibration_weight") or 0) > 0
249
+ and any(cc.keyword == c["normalized_concept"]
250
+ for cc in ev_before.covered)][:6]
251
+ new_latex, srec = optimize_summary(
252
+ final_latex, job_title or "", top_phrases, resume_text, eff_summary_fn)
253
+ final_latex = new_latex
254
+ report["summary_rewrite"] = srec
255
+
256
  # 6. Compile + PDF-parse (so scoring can use PARSED text, not just LaTeX).
257
  _compile_preserved(report, final_latex, out_dir, job_title, compile_pdf,
258
  compile_latex_to_pdf, _safe_jobname, _prog,
 
319
  "mandatory_recall": ev_after.metrics().get("mandatory_recall"),
320
  "scored_from": "parsed_pdf" if report.get("_pdf_text_used") else "latex_text",
321
  }
322
+
323
+ # 8. INDEPENDENT audit + two-parser PDF verification (acceptance gate). The
324
+ # audit re-extracts from the JD independently and scores from the PARSED
325
+ # PDF; the reported acceptance score cannot exceed what the audit confirms.
326
+ if run_audit and llm_client is not None and report.get("_pdf_text_used"):
327
+ try:
328
+ from .ats_evaluate import independent_audit, acceptance_verdict
329
+ from .pdf_validate import _extract_pdf_text, verify_keywords_two_parsers
330
+ pdf_text = _extract_pdf_text(report["pdf_path"]) or score_text
331
+ audit = independent_audit(clean_jd, resume_text, pdf_text, llm_client)
332
+ accepted_kw = [r.get("exact_jd_phrase") for r in rewrite_records
333
+ if r.get("applied")]
334
+ two_parser = verify_keywords_two_parsers(report["pdf_path"], accepted_kw) \
335
+ if accepted_kw else {}
336
+ verdict = acceptance_verdict(audit, 0, stuffing, two_parser)
337
+ report["independent_audit"] = audit
338
+ report["pdf_two_parser"] = two_parser
339
+ report["acceptance"] = verdict
340
+ except Exception as e:
341
+ report["audit_error"] = str(e)[:160]
342
  return report
343
 
344
 
 
486
  report["compile_log"] = comp.get("log", "")
487
  except Exception as e:
488
  report["compile_log"] = f"compile_error: {e}"
489
+
490
+ # ATS-safe fallback: when no LaTeX engine compiled a PDF, render the résumé as
491
+ # a single-column plain-text PDF (reportlab). This is what an ATS reads anyway,
492
+ # and it guarantees a real, parseable PDF on any host (incl. no-tectonic).
493
+ if not report.get("pdf_path"):
494
+ try:
495
+ from .latex_resume import latex_to_text, render_text_to_pdf
496
+ fb = os.path.join(out_dir, f"{jobname}.pdf")
497
+ if render_text_to_pdf(latex_to_text(latex_src), fb) and os.path.exists(fb):
498
+ report["pdf_path"] = fb
499
+ report["engine"] = report.get("engine") or "reportlab-atsafe"
500
+ report["compiled"] = True
501
+ report["pdf_fallback"] = True
502
+ except Exception as e:
503
+ report["compile_log"] = (report.get("compile_log", "") + f" | fallback: {e}")
504
 
505
  if report.get("pdf_path"):
506
  from .pdf_validate import validate_pdf
src/ats_score.py CHANGED
@@ -130,7 +130,13 @@ def score_alignment(criteria: List[dict], evidence: Dict, final_text: str,
130
  s_ss = s_ss if s_ss is not None else 1.0
131
 
132
  pv = pdf_validation or {}
133
- s_parse = (1.0 if pv.get("ok") else (0.5 if pv.get("parser_recovered_text") else 0.0)) if pv else 1.0
 
 
 
 
 
 
134
 
135
  comp = {
136
  "mandatory": s_mand, "critical": s_crit, "exact_phrase": s_exact,
@@ -155,7 +161,7 @@ def score_alignment(criteria: List[dict], evidence: Dict, final_text: str,
155
  and (cov["critical_family_coverage"] or 0) >= GATE_90["critical_family_coverage"]
156
  and (cov["critical_exact_phrase_coverage"] or 0) >= GATE_90["critical_exact_phrase_coverage"]
157
  and stuffing_penalty == 0
158
- and (not pv or pv.get("ok") is not False)
159
  )
160
  if score >= 90 and not gate_ok:
161
  score = 89.0
 
130
  s_ss = s_ss if s_ss is not None else 1.0
131
 
132
  pv = pdf_validation or {}
133
+ # Parsing quality hinges on: text recovered + no hidden/injected markers.
134
+ # (Exact section-heading naming is a diagnostic, not a scoring blocker.)
135
+ if pv:
136
+ _parse_clean = pv.get("parser_recovered_text") and not pv.get("forbidden_markers_found")
137
+ s_parse = 1.0 if _parse_clean else (0.5 if pv.get("parser_recovered_text") else 0.0)
138
+ else:
139
+ s_parse = 1.0
140
 
141
  comp = {
142
  "mandatory": s_mand, "critical": s_crit, "exact_phrase": s_exact,
 
161
  and (cov["critical_family_coverage"] or 0) >= GATE_90["critical_family_coverage"]
162
  and (cov["critical_exact_phrase_coverage"] or 0) >= GATE_90["critical_exact_phrase_coverage"]
163
  and stuffing_penalty == 0
164
+ and (not pv or (pv.get("parser_recovered_text") and not pv.get("forbidden_markers_found")))
165
  )
166
  if score >= 90 and not gate_ok:
167
  score = 89.0
src/llm_client.py CHANGED
@@ -76,7 +76,7 @@ class LLMClient:
76
  # ──────────────────────────────────────────────────────────
77
  # STRUCTURED KEYWORD EXTRACTION — injection-resistant, schema'd
78
  # ──────────────────────────────────────────────────────────
79
- def extract_keywords_structured(self, clean_jd: str) -> list[dict]:
80
  """Extract structured, traceable hiring criteria from an ALREADY-CLEANED JD.
81
 
82
  The JD text is treated strictly as untrusted DATA delimited by fences. The
@@ -118,10 +118,14 @@ class LLMClient:
118
  "marketing copy, and generic adjectives.\n"
119
  "- Prefer 15-25 high-value criteria over a long weak list."
120
  )
 
 
 
121
  user = (
122
  "<<<JD_START>>>\n"
123
  f"{(clean_jd or '')[:6000]}\n"
124
- "<<<JD_END>>>\n\n"
 
125
  "Return the JSON array now."
126
  )
127
  try:
@@ -174,6 +178,35 @@ class LLMClient:
174
  print(f"[rewrite_bullet] failed: {e}")
175
  return original_bullet
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  # ──────────────────────────────────────────────────────────
178
  # KEYWORD EXTRACTION — Calibrated Keyword Match Framework (legacy flat list)
179
  # ──────────────────────────────────────────────────────────
 
76
  # ──────────────────────────────────────────────────────────
77
  # STRUCTURED KEYWORD EXTRACTION — injection-resistant, schema'd
78
  # ──────────────────────────────────────────────────────────
79
+ def extract_keywords_structured(self, clean_jd: str, correction_hint: str = "") -> list[dict]:
80
  """Extract structured, traceable hiring criteria from an ALREADY-CLEANED JD.
81
 
82
  The JD text is treated strictly as untrusted DATA delimited by fences. The
 
118
  "marketing copy, and generic adjectives.\n"
119
  "- Prefer 15-25 high-value criteria over a long weak list."
120
  )
121
+ hint = ("\n\nIMPORTANT: your previous extraction MISSED these source-grounded "
122
+ f"requirements — include them if present verbatim: {correction_hint}"
123
+ if correction_hint else "")
124
  user = (
125
  "<<<JD_START>>>\n"
126
  f"{(clean_jd or '')[:6000]}\n"
127
+ "<<<JD_END>>>\n"
128
+ f"{hint}\n"
129
  "Return the JSON array now."
130
  )
131
  try:
 
178
  print(f"[rewrite_bullet] failed: {e}")
179
  return original_bullet
180
 
181
+ def rewrite_summary(self, original_summary: str, target_title: str,
182
+ top_phrases: list, resume_corpus: str) -> str:
183
+ """Rewrite the résumé SUMMARY to target the role, using ONLY facts already
184
+ in the résumé. Output re-verified against the corpus by the caller."""
185
+ system = (
186
+ "You rewrite a résumé professional-summary paragraph to target a "
187
+ "specific role, staying strictly truthful.\n"
188
+ "HARD RULES:\n"
189
+ "- Use ONLY facts, skills, metrics, and experience already present in "
190
+ "the RÉSUMÉ CORPUS. Do not add any new employer, tool, metric, number, "
191
+ "industry, title, or claim not already in the corpus.\n"
192
+ "- Naturally incorporate the TARGET PHRASES only where the corpus "
193
+ "genuinely supports them; skip any that would be untrue.\n"
194
+ "- Keep it 2-4 sentences, natural and recruiter-readable. Not a keyword list.\n"
195
+ "- Return ONLY the rewritten summary text."
196
+ )
197
+ user = (
198
+ f"TARGET TITLE: {target_title}\n"
199
+ f"TARGET PHRASES (use only if truthful): {', '.join(top_phrases)}\n\n"
200
+ f"RÉSUMÉ CORPUS (the only allowed source of facts):\n{resume_corpus[:3500]}\n\n"
201
+ f"ORIGINAL SUMMARY:\n{original_summary}\n\n"
202
+ "Rewritten summary:"
203
+ )
204
+ try:
205
+ return (self._call(system, user, max_tokens=500) or "").strip().strip('"')
206
+ except Exception as e:
207
+ print(f"[rewrite_summary] failed: {e}")
208
+ return original_summary
209
+
210
  # ──────────────────────────────────────────────────────────
211
  # KEYWORD EXTRACTION — Calibrated Keyword Match Framework (legacy flat list)
212
  # ──────────────────────────────────────────────────────────
src/nim_fallback.py CHANGED
@@ -109,6 +109,32 @@ def reset_cache() -> None:
109
  _CACHE["logs"] = []
110
 
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  def select_model(chain: Optional[List[str]] = None,
113
  timeout: float = 25.0, use_cache: bool = True) -> Tuple[Optional[str], List[dict]]:
114
  """Return (first_healthy_model | None, per-model health logs)."""
 
109
  _CACHE["logs"] = []
110
 
111
 
112
+ def extraction_gaps(clean_jd: str, valid_items: list) -> list:
113
+ """Deterministic completeness check: obvious source-grounded requirements the
114
+ extraction should have captured but didn't. Returns a list of missed phrases
115
+ (named tools present in the JD, an explicit years-of-experience requirement).
116
+ Used to trigger a correction retry / failover before trusting an extraction."""
117
+ import re
118
+ try:
119
+ from .jd_analyzer import _TOOLS
120
+ except Exception:
121
+ _TOOLS = set()
122
+ low = (clean_jd or "").lower()
123
+ got = " ".join((v.get("exact_phrase", "") + " " + v.get("normalized_concept", ""))
124
+ for v in (valid_items or [])).lower()
125
+ missed = []
126
+ # named tools/technologies present verbatim in the JD but absent from criteria
127
+ for tool in _TOOLS:
128
+ if re.search(r"(?<![a-z0-9])" + re.escape(tool) + r"(?![a-z0-9])", low) \
129
+ and tool not in got:
130
+ missed.append(tool)
131
+ # explicit years-of-experience requirement (e.g. "10+ years")
132
+ ym = re.search(r"\b(\d{1,2}\+?\s*years?)\b", low)
133
+ if ym and "year" not in got:
134
+ missed.append(ym.group(1))
135
+ return missed[:12]
136
+
137
+
138
  def select_model(chain: Optional[List[str]] = None,
139
  timeout: float = 25.0, use_cache: bool = True) -> Tuple[Optional[str], List[dict]]:
140
  """Return (first_healthy_model | None, per-model health logs)."""
src/pdf_validate.py CHANGED
@@ -34,6 +34,64 @@ def _extract_pdf_text(pdf_path: str) -> Optional[str]:
34
  return None
35
 
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  def validate_pdf(
38
  pdf_path: str,
39
  *,
@@ -67,10 +125,13 @@ def validate_pdf(
67
  expected_sections = expected_sections or ["experience", "education", "skills"]
68
 
69
  def _heading_pos(sec: str) -> int:
 
70
  for m in re.finditer(r"(?im)^[^\S\n]*([A-Za-z &/]{3,40})[^\S\n]*$", text):
71
  line = m.group(1).strip().lower()
72
- if line == sec.lower() or line.startswith(sec.lower() + " ") \
73
- or line.rstrip("s") == sec.lower().rstrip("s"):
 
 
74
  return m.start()
75
  return -1
76
 
 
34
  return None
35
 
36
 
37
+ def _pdftotext_extract(pdf_path: str) -> Optional[str]:
38
+ """Independent parser #2: the poppler `pdftotext` CLI. None if unavailable."""
39
+ import shutil
40
+ import subprocess
41
+ exe = shutil.which("pdftotext")
42
+ if not exe:
43
+ return None
44
+ try:
45
+ out = subprocess.run([exe, "-layout", pdf_path, "-"],
46
+ capture_output=True, timeout=30)
47
+ txt = out.stdout.decode("utf-8", errors="replace")
48
+ return txt if txt.strip() else None
49
+ except Exception:
50
+ return None
51
+
52
+
53
+ def verify_keywords_two_parsers(pdf_path: str, keywords: List[str]) -> Dict:
54
+ """Confirm each accepted keyword survives BOTH independent parsers (a Python
55
+ lib + poppler's pdftotext). Flags any keyword split/corrupted/missing in
56
+ either. Falls back to a second Python parser (pymupdf) when pdftotext is
57
+ absent, so there are always two independent extractions."""
58
+ import re as _re
59
+ py = _extract_pdf_text(pdf_path) or ""
60
+ cli = _pdftotext_extract(pdf_path)
61
+ parser2_name = "pdftotext"
62
+ if cli is None:
63
+ # fall back to a genuinely different Python engine
64
+ try:
65
+ import fitz
66
+ doc = fitz.open(pdf_path)
67
+ cli = "\n".join(p.get_text() for p in doc)
68
+ doc.close()
69
+ parser2_name = "pymupdf"
70
+ except Exception:
71
+ cli = ""
72
+ def _present(text, kw):
73
+ p = _re.sub(r"\s+", " ", (kw or "").lower()).strip()
74
+ toks = [_re.escape(t) for t in p.split()]
75
+ if not toks:
76
+ return False
77
+ pat = r"(?<![a-z0-9])" + r"[\s\W]{0,3}".join(toks) + r"(?![a-z0-9])"
78
+ return _re.search(pat, text.lower()) is not None
79
+ results = []
80
+ for kw in keywords:
81
+ in_py, in_cli = _present(py, kw), _present(cli, kw)
82
+ results.append({"keyword": kw, "parser1_pdfplumber": in_py,
83
+ f"parser2_{parser2_name}": in_cli,
84
+ "in_both": in_py and in_cli})
85
+ return {
86
+ "parser1": "pdfplumber/pymupdf", "parser2": parser2_name,
87
+ "parser1_chars": len(py), "parser2_chars": len(cli),
88
+ "keywords_checked": len(keywords),
89
+ "present_in_both": sum(1 for r in results if r["in_both"]),
90
+ "missing_or_split": [r["keyword"] for r in results if not r["in_both"]],
91
+ "per_keyword": results,
92
+ }
93
+
94
+
95
  def validate_pdf(
96
  pdf_path: str,
97
  *,
 
125
  expected_sections = expected_sections or ["experience", "education", "skills"]
126
 
127
  def _heading_pos(sec: str) -> int:
128
+ s = sec.lower()
129
  for m in re.finditer(r"(?im)^[^\S\n]*([A-Za-z &/]{3,40})[^\S\n]*$", text):
130
  line = m.group(1).strip().lower()
131
+ # near-exact heading line only (résumé headings stand alone) — never a
132
+ # prose line that merely starts with the section word.
133
+ if line == s or line.rstrip("s") == s.rstrip("s") \
134
+ or (line.startswith(s) and len(line) <= len(s) + 3):
135
  return m.start()
136
  return -1
137
 
src/resume_rewrite.py CHANGED
@@ -341,6 +341,91 @@ def _rebuild_bullet(b: dict, new_text: str, latex_escape) -> str:
341
  return r"\item " + esc
342
 
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  # ── Reference (deterministic) rewriter — demo/offline stand-in for the LLM ──────
345
 
346
  def reference_rewrite_fn(original: str, target_phrase: str,
 
341
  return r"\item " + esc
342
 
343
 
344
+ # ── Headline / summary optimization (corpus-verified prose rewriting) ───────────
345
+
346
+ def verify_against_corpus(new_text: str, corpus_text: str,
347
+ target_phrases) -> Tuple[bool, str]:
348
+ """Fabrication guard for prose (summary/headline). The rewrite may reuse ANY
349
+ fact already in the résumé corpus + the target JD phrases + connective words,
350
+ but must introduce NO new number and NO new content token beyond those."""
351
+ phrases = [target_phrases] if isinstance(target_phrases, str) else list(target_phrases or [])
352
+ if not (new_text or "").strip():
353
+ return False, "empty"
354
+ if _KEYWORD_LIST_RE.search(new_text):
355
+ return False, "keyword_list_pattern"
356
+ if not _nums(new_text) <= _nums(corpus_text):
357
+ return False, "new_metric_introduced"
358
+ corpus_tok = {t.lower() for t in _content_tokens(corpus_text)}
359
+ target_tok = {t.lower() for ph in phrases for t in _content_tokens(ph)}
360
+ for t in _content_tokens(new_text):
361
+ tl = t.lower()
362
+ if tl in corpus_tok or tl in target_tok or tl in _ALLOWED_NEW:
363
+ continue
364
+ if any(tl.startswith(c[:4]) and abs(len(tl) - len(c)) <= 3
365
+ for c in corpus_tok if len(c) >= 4):
366
+ continue
367
+ return False, f"new_content_token:{tl}"
368
+ return True, "ok"
369
+
370
+
371
+ def locate_summary(latex_src: str):
372
+ """Return (start, end, inner) of the SUMMARY paragraph's \\small{...} content,
373
+ or None. Matches the résumé's `\\section{SUMMARY} ... \\small{ <inner> }`."""
374
+ m = re.search(r"\\section\*?\{\s*(?:summary|professional summary|profile)\s*\}",
375
+ latex_src, re.I)
376
+ if not m:
377
+ return None
378
+ k = latex_src.find(r"\small{", m.end())
379
+ if k < 0 or k - m.end() > 400:
380
+ return None
381
+ open_brace = k + len(r"\small")
382
+ depth, i = 0, open_brace
383
+ while i < len(latex_src):
384
+ if latex_src[i] == "{":
385
+ depth += 1
386
+ elif latex_src[i] == "}":
387
+ depth -= 1
388
+ if depth == 0:
389
+ return (open_brace + 1, i, latex_src[open_brace + 1:i])
390
+ i += 1
391
+ return None
392
+
393
+
394
+ def optimize_summary(latex_src: str, target_title: str, top_phrases: List[str],
395
+ corpus_text: str, summary_fn) -> Tuple[str, dict]:
396
+ """Rewrite the SUMMARY paragraph via `summary_fn`, verified against the résumé
397
+ corpus. `summary_fn(original, target_title, top_phrases, corpus) -> str`.
398
+ Returns (new_latex, record). Keeps original on any verification failure."""
399
+ from .latex_resume import latex_to_text, latex_escape
400
+ loc = locate_summary(latex_src)
401
+ rec = {"kind": "summary", "applied": False, "reject_reason": "",
402
+ "original": "", "rewritten": "", "change_type": "no_change_required"}
403
+ if not loc:
404
+ rec["reject_reason"] = "no_summary_section"
405
+ return latex_src, rec
406
+ s, e, inner = loc
407
+ original_plain = re.sub(r"\s+", " ", latex_to_text(inner)).strip()
408
+ rec["original"] = original_plain
409
+ try:
410
+ proposed = summary_fn(original_plain, target_title, top_phrases, corpus_text)
411
+ except Exception as ex:
412
+ rec["reject_reason"] = f"summary_fn_error:{str(ex)[:60]}"
413
+ return latex_src, rec
414
+ proposed = re.sub(r"\s+", " ", (proposed or "")).strip()
415
+ rec["rewritten"] = proposed
416
+ if not proposed or proposed == original_plain:
417
+ rec["reject_reason"] = "no_change"
418
+ return latex_src, rec
419
+ ok, why = verify_against_corpus(proposed, corpus_text, top_phrases)
420
+ if not ok:
421
+ rec["reject_reason"] = why
422
+ return latex_src, rec
423
+ rec["applied"] = True
424
+ rec["change_type"] = "summary_optimization"
425
+ new_latex = latex_src[:s] + latex_escape(proposed) + latex_src[e:]
426
+ return new_latex, rec
427
+
428
+
429
  # ── Reference (deterministic) rewriter — demo/offline stand-in for the LLM ──────
430
 
431
  def reference_rewrite_fn(original: str, target_phrase: str,