"""Validation and safety partitioning for resume optimization proposals.""" from __future__ import annotations import re from typing import Any from .experience_optimizer import normalize_fact_ledger from .text_normalization import decode_literal_unicode_escapes _NUMBER = re.compile(r"\d+(?:\.\d+)?%?") _LATIN_TERM = re.compile(r"[A-Za-z][A-Za-z0-9.+#_-]{1,}") _COMMON_TECH_TERMS = frozenset({ "aws", "azure", "docker", "elasticsearch", "fastapi", "flask", "git", "go", "java", "javascript", "kafka", "kubernetes", "langchain", "langgraph", "linux", "mongodb", "mysql", "nextjs", "nodejs", "numpy", "openai", "pandas", "postgresql", "python", "pytorch", "rabbitmq", "react", "redis", "spring", "sql", "tensorflow", "typescript", "vue", "vue3", }) _SENTENCE = re.compile(r"(?<=[。!?!?;;])\s*|\n+") _COUNTED_OBJECT = re.compile( r"(?P\d+(?:\.\d+)?)(?:\s*)(?P名|位|人|项|个|次|台|条|份|家|天|月|年|students?|classmates?|users?|features?|services?|projects?|requests?)(?:\s*)(?P[A-Za-z][A-Za-z -]{0,24}|[\u4e00-\u9fff]{0,8})", re.I, ) def validate_proposal(proposal: dict[str, Any], facts: list[Any]) -> dict[str, Any]: """Normalize proposal metadata without suppressing useful model-written prose. The fact ledger validates claim references and aids diagnostics. It is not a word-for-word acceptance gate for optimized prose: resume editing needs paraphrase, synthesis, and controlled role-oriented expansion. """ result = decode_literal_unicode_escapes(dict(proposal)) ledger = normalize_fact_ledger(facts) known_ids = {item["id"] for item in ledger} evidence = "\n".join(item["text"] for item in ledger) warnings = [str(item) for item in result.get("validation_warnings") or [] if str(item)] suggestions = [str(item).strip() for item in result.get("unconfirmed_suggestions") or [] if str(item).strip()] optional_enhancements = [ str(item).strip() for item in result.get("optional_enhancements") or [] if str(item).strip() ] valid_claims: list[dict[str, Any]] = [] for raw_claim in result.get("claims") or []: claim = dict(raw_claim) if isinstance(raw_claim, dict) else {} text = str(claim.get("text") or "").strip() evidence_ids = [str(item) for item in claim.get("evidence_ids") or []] if not text: _warn(warnings, "empty_claim") continue if not evidence_ids or any(item.startswith("rag_") or item not in known_ids for item in evidence_ids): _warn(warnings, "unsupported_evidence_reference") continue valid_claims.append(claim) optimized, quarantined = _partition_text(str(result.get("optimized_description") or "").strip(), evidence, []) bullets: list[str] = [] for value in result.get("bullets") or []: bullet, bullet_suggestions = _partition_text(str(value).strip(), evidence, []) quarantined.extend(bullet_suggestions) if bullet: bullets.append(bullet) if not optimized and quarantined: optimized = _primary_description(ledger) _warn(warnings, "candidate_contains_unconfirmed_additions") if quarantined: _warn(warnings, "suggestion_requires_confirmation") suggestions.extend(quarantined) result["claims"] = valid_claims result["optimized_description"] = optimized result["bullets"] = list(dict.fromkeys(bullets))[:5] result["unconfirmed_suggestions"] = list(dict.fromkeys(suggestions))[:6] result["optional_enhancements"] = list(dict.fromkeys(optional_enhancements))[:6] if warnings: result["validation_warnings"] = list(dict.fromkeys(warnings)) return result def partition_entry_text(text: str, facts: list[Any]) -> tuple[str, list[str], list[str]]: """Diagnose unsupported signatures without deleting a complete bullet. Candidate text remains visible for user review. Removing an entire bullet because one number or technical term needs confirmation previously discarded confirmed facts in the same statement. """ ledger = normalize_fact_ledger(facts) evidence = "\n".join(item["text"] for item in ledger) suggestions = [ sentence.strip() for sentence in _SENTENCE.split(text.strip()) if sentence.strip() and _has_unconfirmed_signature(sentence.strip(), evidence) ] warnings = ["candidate_requires_confirmation"] if suggestions else [] return text.strip(), suggestions, warnings def _rejoin_sentences(sentences: list[str], *, had_line_breaks: bool) -> str: """Rejoin partitioned sentences, keeping one-statement-per-line layout. Bullet-style candidates are written one per line; flattening them with spaces would cram the whole description into a single paragraph. """ separator = "\n" if had_line_breaks else " " return separator.join(sentences).strip() def are_quantified_facts_grounded(text: str, evidence: str) -> bool: return set(_NUMBER.findall(text)).issubset(set(_NUMBER.findall(evidence))) def is_grounded_resume_text(text: str, evidence: str) -> bool: return are_quantified_facts_grounded(text, evidence) and _technical_terms(text).issubset(_technical_terms(evidence)) def quantified_fact_contexts(evidence: str) -> list[dict[str, str]]: contexts: list[dict[str, str]] = [] for clause in re.split(r"[。!?!?;;.\n]+", evidence): clean = clause.strip() for number in _NUMBER.findall(clean): contexts.append({"number": number, "unit": "", "context": clean}) return contexts[:12] def _partition_text(text: str, evidence: str, invalid_claim_texts: list[str]) -> tuple[str, list[str]]: # Do not use lexical overlap as an acceptance gate. Models commonly turn a # user sentence into several resume bullets or use a stronger role-oriented # paraphrase; hiding those sentences invokes rule fallbacks needlessly. del evidence, invalid_claim_texts return text.strip(), [] def _has_unconfirmed_signature(text: str, evidence: str) -> bool: if not _technical_terms(text).issubset(_technical_terms(evidence)): return True known_by_number: dict[str, set[tuple[str, str]]] = {} for number, unit, object_name in _counted_objects(evidence): known_by_number.setdefault(number, set()).add((unit, object_name)) for number, unit, object_name in _counted_objects(text): known = known_by_number.get(number) if known and (unit, object_name) not in known: return True evidence_numbers = {number.rstrip("%") for number in _NUMBER.findall(evidence)} # A percentage paraphrase ("前百分之10" -> "前 10%") is the same fact; the # grounding gate compares numeric values, not surface percent signs. return any(number.rstrip("%") not in evidence_numbers for number in _NUMBER.findall(text)) def _technical_terms(text: str) -> set[str]: return {term.casefold().rstrip(".,;:!?") for term in _LATIN_TERM.findall(text) if term.casefold().rstrip(".,;:!?") in _COMMON_TECH_TERMS} def _counted_objects(text: str) -> list[tuple[str, str, str]]: values: list[tuple[str, str, str]] = [] for match in _COUNTED_OBJECT.finditer(text): unit = match.group("unit").casefold() object_name = "" if unit.isascii() else match.group("object").strip().casefold()[:24] values.append((match.group("number"), unit, object_name)) return values def _primary_description(ledger: list[dict[str, str]]) -> str: return next((item["text"] for item in ledger if item.get("field") == "description"), "") def _normalize(text: str) -> str: return "".join(character.casefold() for character in text if character.isalnum()) def _warn(warnings: list[str], value: str) -> None: if value not in warnings: warnings.append(value)