#!/usr/bin/env python3 """Prepare public-text labeling queues and build deterministic consensus silver data.""" from __future__ import annotations import argparse import hashlib import json import math import random import re import unicodedata from collections import Counter, defaultdict from pathlib import Path SEED = 20260827 INTENT_LABELS = ( "task", "question", "invitation", "complaint", "scheduleNegotiation", "confirmationDecision", "followUpReminder", "blessing", "replyableMessage", ) SPECIAL_FLAGS = ("ambiguous", "quotedOrMeta") ACTION_LABELS = ( "taskOnly", "complaintOnly", "both", "questionRequest", "neither", ) COORDINATION_LABELS = ( "invitation", "scheduleNegotiation", "confirmationDecision", "followUpReminder", "neither", ) PROMPT_VERSION = "clipboard-consensus-v1" DEFAULT_DIRECTORY = Path("ModelTraining/ClipboardSemantics/Consensus") DEFAULT_INPUT = Path("ModelTraining/ClipboardSemantics/open-training-corpus.jsonl") def normalized_text(value: str) -> str: return " ".join( unicodedata.normalize("NFKC", value) .replace("\u0000", " ") .split() ).strip() def stable_hash(value: str) -> int: return int.from_bytes(hashlib.sha256(value.encode()).digest()[:8], "big") def read_json_lines(path: Path) -> list[dict]: return [ json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip() ] def write_json_lines(path: Path, records: list[dict]) -> None: path.parent.mkdir(parents=True, exist_ok=True) serialized = "\n".join( json.dumps(record, ensure_ascii=False, sort_keys=True) for record in records ) path.write_text(serialized + ("\n" if serialized else ""), encoding="utf-8") def queue_priority( record: dict, hard_negative_texts: set[str], ) -> tuple[int, int]: known_labels = set(record.get("knownLabels") or []) positive_count = sum(bool(record.get(label)) for label in INTENT_LABELS) confusion_priority = int( bool( known_labels & { "task", "question", "complaint", "invitation", "scheduleNegotiation", "confirmationDecision", "followUpReminder", } ) ) hard_negative_priority = int( normalized_text(record["text"]).casefold() in hard_negative_texts ) return ( hard_negative_priority * 10 + confusion_priority + positive_count, stable_hash(record["id"]), ) def stratified_queue( records: list[dict], count: int, seed: int, hard_negative_texts: set[str], ) -> list[dict]: grouped: dict[tuple[str, str], list[dict]] = defaultdict(list) for record in records: grouped[ ( record.get("sourceDataset", "unknown"), record.get("language", "unknown"), ) ].append(record) for key in grouped: grouped[key].sort( key=lambda record: queue_priority(record, hard_negative_texts), reverse=True, ) keys = sorted(grouped) rng = random.Random(seed) rng.shuffle(keys) offsets = {key: 0 for key in keys} selected: list[dict] = [] while len(selected) < count: added = False for key in keys: offset = offsets[key] values = grouped[key] if offset >= len(values): continue selected.append(values[offset]) offsets[key] += 1 added = True if len(selected) == count: break if not added: break return sorted(selected, key=lambda record: record["id"]) def labeling_instructions() -> str: return """# Clipboard semantic consensus labeling Prompt version: clipboard-consensus-v1 Label every JSONL queue record independently. Use only the text itself; do not infer missing conversational context. Return one JSON object per input record: {"id":"same id","labels":["task"],"ambiguous":false,"quotedOrMeta":false,"confidence":0.98} Allowed labels: - task: another person is explicitly asked/assigned to perform an action. - question: a genuine information question, including request-shaped questions. - invitation: invitation to join an event or social activity. - complaint: present dissatisfaction, malfunction, bad service, or unresolved problem. - scheduleNegotiation: proposing, changing, or choosing between times; a fixed time is not enough. - confirmationDecision: explicit approval, rejection, or selection of an option. - followUpReminder: request to remind, check back, or follow up later/after a trigger. - blessing: a genuine birthday, holiday, congratulations, or good-wish message. - replyableMessage: a direct conversational message that naturally invites a response. Rules: - Multi-label is allowed. "Could you send it?" is task + question + replyableMessage. - Set quotedOrMeta when intent-like words are quoted, documented, searched, or discussed. - Set ambiguous when the text cannot be labeled without missing context. - Empty labels mean none of the product intents. - Negative sentiment alone is not complaint. Fixed appointments are not schedule negotiation. - Do not expose reasoning or add fields. Confidence must be between 0 and 1. """ def prepare(arguments: argparse.Namespace) -> None: records = read_json_lines(arguments.input) requested_hard_negatives: set[str] = set() for path in arguments.hard_negative_report: report = json.loads(path.read_text(encoding="utf-8")) for classifier in report.get("classifiers") or []: for example in classifier.get("falsePositiveExamples") or []: requested_hard_negatives.add( normalized_text(example["text"]).casefold() ) license_safe_texts = { normalized_text(record["text"]).casefold() for record in records } matched_hard_negatives = requested_hard_negatives & license_safe_texts queue = stratified_queue( records, arguments.count, arguments.seed, matched_hard_negatives, ) queue_records = [ { "id": record["id"], "text": record["text"], "language": record["language"], "sourceDataset": record.get("sourceDataset"), "sourceLicense": record.get("sourceLicense"), "sourceURL": record.get("sourceURL"), "sourceRevision": record.get("sourceRevision"), "knownLabels": record.get("knownLabels", []), "sourceLabels": { label: bool(record.get(label)) for label in INTENT_LABELS if label in set(record.get("knownLabels") or []) }, } for record in queue ] write_json_lines(arguments.queue, queue_records) arguments.instructions.parent.mkdir(parents=True, exist_ok=True) instructions = labeling_instructions() arguments.instructions.write_text(instructions, encoding="utf-8") manifest = { "schemaVersion": 1, "promptVersion": PROMPT_VERSION, "promptSHA256": hashlib.sha256(instructions.encode()).hexdigest(), "seed": arguments.seed, "input": str(arguments.input), "queue": str(arguments.queue), "recordCount": len(queue_records), "requestedHardNegativeCount": len(requested_hard_negatives), "licenseSafeMatchedHardNegativeCount": len(matched_hard_negatives), "sourceCounts": dict( sorted( Counter( record.get("sourceDataset") or "unknown" for record in queue_records ).items() ) ), "languageCounts": dict( sorted(Counter(record["language"] for record in queue_records).items()) ), } arguments.prepare_report.write_text( json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8", ) print( f"CONSENSUS_QUEUE records={len(queue_records)} " f"promptSHA256={manifest['promptSHA256']}" ) def parse_labeler_argument(value: str) -> tuple[str, Path]: name, separator, raw_path = value.partition("=") if not separator or not name or not raw_path: raise argparse.ArgumentTypeError("Expected LABELER=PATH") return name, Path(raw_path) def validate_labeler_record(record: dict, expected_ids: set[str]) -> dict: record_id = record.get("id") if record_id not in expected_ids: raise ValueError(f"Unexpected labeler record id: {record_id}") labels = record.get("labels") if not isinstance(labels, list) or any(label not in INTENT_LABELS for label in labels): raise ValueError(f"Unsupported labels for {record_id}: {labels}") confidence = record.get("confidence") if not isinstance(confidence, (int, float)) or not 0 <= confidence <= 1: raise ValueError(f"Invalid confidence for {record_id}: {confidence}") for flag in SPECIAL_FLAGS: if not isinstance(record.get(flag), bool): raise ValueError(f"Missing boolean {flag} for {record_id}") return { "id": record_id, "labels": sorted(set(labels)), "ambiguous": record["ambiguous"], "quotedOrMeta": record["quotedOrMeta"], "confidence": round(float(confidence), 4), } def action_label(labels: set[str]) -> str: if "task" in labels and "complaint" in labels: return "both" if "task" in labels: return "taskOnly" if "complaint" in labels: return "complaintOnly" if "question" in labels: return "questionRequest" return "neither" def coordination_label(labels: set[str]) -> str | None: matches = [ label for label in COORDINATION_LABELS if label != "neither" and label in labels ] if len(matches) > 1: return None return matches[0] if matches else "neither" def cluster_signature(text: str) -> str: normalized = unicodedata.normalize("NFKC", text).casefold() normalized = re.sub(r"\d+", "", normalized) normalized = re.sub(r"[^\w\u4e00-\u9fff<>]+", " ", normalized) tokens = normalized.split() # Prefix and suffix retain intent-bearing wording while grouping slot variants. skeleton = tokens[:10] + (["|"] + tokens[-6:] if len(tokens) > 16 else []) return " ".join(skeleton) def split_for(record: dict) -> str: bucket = stable_hash(cluster_signature(record["text"])) % 100 if bucket < 70: return "silverTrain" if bucket < 85: return "silverCalibration" return "silverAcceptance" def fleiss_kappa( labeler_records: list[dict[str, dict]], queue_ids: list[str], ) -> float: if len(labeler_records) < 2 or not queue_ids: return 0 category_counts = [0, 0] agreement_total = 0.0 item_count = 0 rater_count = len(labeler_records) for record_id in queue_ids: for label in INTENT_LABELS: yes_count = sum( label in set(labeler[record_id]["labels"]) for labeler in labeler_records ) no_count = rater_count - yes_count category_counts[0] += no_count category_counts[1] += yes_count agreement_total += ( no_count * (no_count - 1) + yes_count * (yes_count - 1) ) / (rater_count * (rater_count - 1)) item_count += 1 observed = agreement_total / item_count total_votes = sum(category_counts) expected = sum((count / total_votes) ** 2 for count in category_counts) if math.isclose(expected, 1): return 1 return round((observed - expected) / (1 - expected), 4) def merge(arguments: argparse.Namespace) -> None: queue = read_json_lines(arguments.queue) queue_by_id = {record["id"]: record for record in queue} if len(queue_by_id) != len(queue): raise ValueError("Consensus queue contains duplicate ids") expected_ids = set(queue_by_id) if len(arguments.labeler) < 3: raise ValueError("Consensus requires at least three independent labelers") labelers: list[tuple[str, dict[str, dict]]] = [] for name, path in arguments.labeler: values = [ validate_labeler_record(record, expected_ids) for record in read_json_lines(path) ] by_id = {record["id"]: record for record in values} missing = expected_ids.difference(by_id) if missing: raise ValueError(f"Labeler {name} is missing {len(missing)} records") if len(by_id) != len(values): raise ValueError(f"Labeler {name} contains duplicate ids") labelers.append((name, by_id)) accepted: list[dict] = [] conflicts: list[dict] = [] for record_id in sorted(expected_ids): queue_record = queue_by_id[record_id] votes = Counter( label for _, records in labelers for label in records[record_id]["labels"] ) source_labels = { label for label, value in (queue_record.get("sourceLabels") or {}).items() if value } consensus_labels = { label for label, count in votes.items() if count == len(labelers) or count >= 2 and label in source_labels } ambiguous_votes = sum( records[record_id]["ambiguous"] for _, records in labelers ) quoted_votes = sum( records[record_id]["quotedOrMeta"] for _, records in labelers ) coordination = coordination_label(consensus_labels) full_agreement = all( set(records[record_id]["labels"]) == set( labelers[0][1][record_id]["labels"] ) and records[record_id]["ambiguous"] == labelers[0][1][record_id]["ambiguous"] and records[record_id]["quotedOrMeta"] == labelers[0][1][record_id]["quotedOrMeta"] for _, records in labelers[1:] ) rejected_reason = None if ambiguous_votes >= 2: rejected_reason = "ambiguous-majority" elif quoted_votes >= 2 and consensus_labels: rejected_reason = "quoted-or-meta-intent" elif coordination is None: rejected_reason = "multiple-coordination-labels" elif not consensus_labels and not full_agreement: rejected_reason = "no-supported-consensus" audit = { "id": record_id, "labelerVotes": dict(sorted(votes.items())), "labelerConfidences": { name: records[record_id]["confidence"] for name, records in labelers }, "labelerResponseHashes": { name: hashlib.sha256( json.dumps( records[record_id], ensure_ascii=False, sort_keys=True, separators=(",", ":"), ).encode() ).hexdigest() for name, records in labelers }, "sourceLabels": sorted(source_labels), "ambiguousVotes": ambiguous_votes, "quotedOrMetaVotes": quoted_votes, "fullAgreement": full_agreement, } if rejected_reason: conflicts.append( { **queue_record, **audit, "rejectedReason": rejected_reason, } ) continue split = split_for(queue_record) labels = set() if quoted_votes >= 2 else consensus_labels silver = { "id": f"consensus-{record_id}", "sourceRecordID": record_id, "text": queue_record["text"], "language": queue_record["language"], "family": "consensus_public", "split": split, **{label: label in labels for label in INTENT_LABELS}, "sentiment": "neutral", "replyable": "replyableMessage" in labels, "actionVerifierLabel": action_label(labels), "coordinationVerifierLabel": coordination_label(labels) or "neither", "quotedOrMeta": quoted_votes >= 2, "consensusAgreement": round( max( [votes.get(label, 0) for label in INTENT_LABELS] + [ len(labelers) if not labels and full_agreement else 0 ] ) / len(labelers), 4, ), "promptVersion": PROMPT_VERSION, "sourceDataset": queue_record.get("sourceDataset"), "sourceLicense": queue_record.get("sourceLicense"), "sourceURL": queue_record.get("sourceURL"), "sourceRevision": queue_record.get("sourceRevision"), **audit, } accepted.append(silver) write_json_lines(arguments.consensus, accepted) write_json_lines(arguments.conflicts, conflicts) for split, path in ( ("silverTrain", arguments.train), ("silverCalibration", arguments.calibration), ("silverAcceptance", arguments.acceptance), ): write_json_lines( path, [record for record in accepted if record["split"] == split], ) labeler_maps = [records for _, records in labelers] text_split_map: dict[str, set[str]] = defaultdict(set) cluster_split_map: dict[str, set[str]] = defaultdict(set) for record in accepted: text_split_map[normalized_text(record["text"]).casefold()].add(record["split"]) cluster_split_map[cluster_signature(record["text"])].add(record["split"]) exact_overlap_count = sum(len(splits) > 1 for splits in text_split_map.values()) cluster_overlap_count = sum( len(splits) > 1 for splits in cluster_split_map.values() ) if exact_overlap_count or cluster_overlap_count: raise ValueError("Silver split overlap validation failed") report = { "schemaVersion": 1, "promptVersion": PROMPT_VERSION, "promptSHA256": hashlib.sha256(labeling_instructions().encode()).hexdigest(), "queueCount": len(queue), "acceptedCount": len(accepted), "conflictCount": len(conflicts), "conflictRate": round(len(conflicts) / max(len(queue), 1), 4), "duplicateTextRate": round( (len(accepted) - len(text_split_map)) / max(len(accepted), 1), 4, ), "overlapChecks": { "exactTextAcrossSplits": exact_overlap_count, "nearDuplicateClusterAcrossSplits": cluster_overlap_count, }, "fullAgreementCount": sum(record["fullAgreement"] for record in accepted) + sum(record["fullAgreement"] for record in conflicts), "fleissKappa": fleiss_kappa( labeler_maps, sorted(expected_ids), ), "labelers": [name for name, _ in labelers], "splitCounts": dict(sorted(Counter(record["split"] for record in accepted).items())), "actionLabelCounts": dict( sorted(Counter(record["actionVerifierLabel"] for record in accepted).items()) ), "coordinationLabelCounts": dict( sorted( Counter( record["coordinationVerifierLabel"] for record in accepted ).items() ) ), "intentPositiveCounts": { label: sum(bool(record[label]) for record in accepted) for label in INTENT_LABELS }, "languageCounts": dict( sorted(Counter(record["language"] for record in accepted).items()) ), "sourceCounts": dict( sorted( Counter( record.get("sourceDataset") or "unknown" for record in accepted ).items() ) ), } arguments.report.write_text( json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8", ) print( f"CONSENSUS_DONE accepted={len(accepted)} conflicts={len(conflicts)} " f"kappa={report['fleissKappa']}" ) def parser() -> argparse.ArgumentParser: root = argparse.ArgumentParser() subparsers = root.add_subparsers(dest="command", required=True) prepare_parser = subparsers.add_parser("prepare") prepare_parser.add_argument("--input", type=Path, default=DEFAULT_INPUT) prepare_parser.add_argument( "--queue", type=Path, default=DEFAULT_DIRECTORY / "labeling-queue.jsonl", ) prepare_parser.add_argument( "--instructions", type=Path, default=DEFAULT_DIRECTORY / "labeling-instructions.md", ) prepare_parser.add_argument( "--prepare-report", type=Path, default=DEFAULT_DIRECTORY / "labeling-queue-report.json", ) prepare_parser.add_argument("--count", type=int, default=360) prepare_parser.add_argument("--seed", type=int, default=SEED) prepare_parser.add_argument( "--hard-negative-report", action="append", type=Path, default=[], ) prepare_parser.set_defaults(handler=prepare) merge_parser = subparsers.add_parser("merge") merge_parser.add_argument( "--queue", type=Path, default=DEFAULT_DIRECTORY / "labeling-queue.jsonl", ) merge_parser.add_argument( "--labeler", action="append", type=parse_labeler_argument, required=True, ) merge_parser.add_argument( "--consensus", type=Path, default=DEFAULT_DIRECTORY / "consensus-silver.jsonl", ) merge_parser.add_argument( "--conflicts", type=Path, default=DEFAULT_DIRECTORY / "consensus-conflicts.jsonl", ) merge_parser.add_argument( "--train", type=Path, default=DEFAULT_DIRECTORY / "silver-train.jsonl", ) merge_parser.add_argument( "--calibration", type=Path, default=DEFAULT_DIRECTORY / "silver-calibration.jsonl", ) merge_parser.add_argument( "--acceptance", type=Path, default=DEFAULT_DIRECTORY / "silver-acceptance.jsonl", ) merge_parser.add_argument( "--report", type=Path, default=DEFAULT_DIRECTORY / "consensus-report.json", ) merge_parser.set_defaults(handler=merge) return root def main() -> None: arguments = parser().parse_args() arguments.handler(arguments) if __name__ == "__main__": main()