aitrainingdatabroker commited on
Commit
2b825d7
·
verified ·
1 Parent(s): d8f22b7

Add source/gen_synth.py

Browse files
Files changed (1) hide show
  1. source/gen_synth.py +379 -0
source/gen_synth.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Datasets 5 to 10 and the training files of models 4 to 10: invented text, no real person, company or secret (2026-10-30).
2
+
3
+ python hf-work/gen_synth.py
4
+ Each dataset is filled from templates. The TEST templates are never used for training, so a score measures new sentence
5
+ shapes and not only new names. Names, companies and numbers are invented; phone numbers use the fictional 555-01xx range,
6
+ emails use example domains, and keys carry a "demo" prefix.
7
+ """
8
+
9
+ import json
10
+ import random
11
+ import string
12
+ from pathlib import Path
13
+
14
+ ROOT = Path(__file__).resolve().parent.parent
15
+ rng = random.Random(20261030)
16
+
17
+ FIRST = ["Marisol", "Dennick", "Aurelio", "Tamsin", "Liesel", "Bram", "Yusra", "Corwin", "Ilse", "Nadim", "Petra", "Ravindra",
18
+ "Sunniva", "Teodor", "Wren", "Zaida", "Hollis", "Imogen", "Kasimir", "Delphine", "Ezio", "Odalys", "Fenwick", "Galen"]
19
+ LAST = ["Quarrington", "Abernethy", "Okafor", "Lindqvist", "Marchetti", "Pemberton", "Szabo", "Thistlewood", "Hakimi",
20
+ "Brannigan", "Castellanos", "Duchesne", "Fairweather", "Gruber", "Ishikawa", "Valdespino", "Nwosu", "Ostrander"]
21
+ ORGS = ["Larkspur Logistics", "Copperfield Supply", "Harborview Dental", "Northgate Analytics", "Juniper Freight", "Ridgeway Builders",
22
+ "Millbrook Software", "Ashford Insurance Group", "Kingsport Marine", "Willow Creek Foods", "Eastlake Clinics",
23
+ "Brightwater Staffing", "Granite Peak Capital", "Fernhill Media", "Stonebridge Legal", "Tidewater Robotics"]
24
+ STREETS = ["Larkspur Lane", "Copperfield Road", "Harbor View Drive", "Old Mill Court", "Juniper Street", "Ridgeway Avenue",
25
+ "Elmhurst Boulevard", "Quarry Road", "Saltmarsh Way", "Pine Terrace"]
26
+ CITIES = ["Brookhaven", "Millbrook", "Fairhaven", "Ashford", "Kingsport", "Willow Creek", "Eastlake", "Stonebridge", "Tidewater"]
27
+ MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
28
+ DOMAINS = ["example.com", "example.org", "mail.example", "corp.example", "sample.test"]
29
+
30
+
31
+ def person():
32
+ return f"{rng.choice(FIRST)} {rng.choice(LAST)}"
33
+
34
+
35
+ def org():
36
+ return rng.choice(ORGS)
37
+
38
+
39
+ def email(name=None):
40
+ n = (name or person()).lower().replace(" ", ".")
41
+ return f"{n}@{rng.choice(DOMAINS)}"
42
+
43
+
44
+ def phone():
45
+ return f"(555) 01{rng.randint(0, 9)}-{rng.randint(1000, 9999)}"
46
+
47
+
48
+ def address():
49
+ return f"{rng.randint(12, 980)} {rng.choice(STREETS)}, {rng.choice(CITIES)}"
50
+
51
+
52
+ def date():
53
+ return f"{rng.choice(MONTHS)} {rng.randint(1, 28)}, {rng.randint(2024, 2026)}"
54
+
55
+
56
+ def money():
57
+ return f"${rng.randint(120, 48000):,}.{rng.randint(0, 99):02d}"
58
+
59
+
60
+ def code(prefix, n=6):
61
+ return prefix + "".join(rng.choice(string.digits) for _ in range(n))
62
+
63
+
64
+ def key():
65
+ return "demo_key_" + "".join(rng.choice(string.ascii_lowercase + string.digits) for _ in range(20))
66
+
67
+
68
+ def ip():
69
+ return f"10.{rng.randint(0, 250)}.{rng.randint(0, 250)}.{rng.randint(2, 250)}"
70
+
71
+
72
+ def write(kind, name, split, rows):
73
+ out = ROOT / kind / name / "data"
74
+ out.mkdir(parents=True, exist_ok=True)
75
+ with open(out / f"{split}.jsonl", "w", encoding="utf-8", newline="\n") as f:
76
+ for r in rows:
77
+ f.write(json.dumps(r, ensure_ascii=False) + "\n")
78
+ print(f"{kind}/{name}/{split}: {len(rows)} rows")
79
+
80
+
81
+ PRE = ["", "", "Note: ", "FYI, ", "Quick one: ", "Following up. ", "Per our earlier message, ", "Hello. ", "To whom it may concern: ", "Sorry for the delay. "]
82
+ SUF = ["", "", " Thanks.", " Please advise.", " Let me know.", " More detail on request.", " Regards.", " Appreciated."]
83
+
84
+
85
+ def classification(name, label_field, train_t, test_t, fill, per_template, extra=None):
86
+ """train_t / test_t: {label: [template, ...]}. Returns (train_rows, test_rows) and writes the dataset."""
87
+ out = {}
88
+ for split, templates in (("train", train_t), ("test", test_t)):
89
+ rows, seen = [], set()
90
+ for label, tpls in templates.items():
91
+ for t in tpls:
92
+ for _ in range(per_template):
93
+ text = rng.choice(PRE) + t.format(**fill()) + rng.choice(SUF)
94
+ if text in seen:
95
+ continue
96
+ seen.add(text)
97
+ rows.append({"text": text, label_field: label, **({k: v(label) for k, v in extra.items()} if extra else {})})
98
+ rng.shuffle(rows)
99
+ out[split] = rows
100
+ return out
101
+
102
+
103
+ def with_ids(prefix, rows):
104
+ return [{"id": f"{prefix}-{i:04d}", **r} for i, r in enumerate(rows, 1)]
105
+
106
+
107
+ def spans_from(segments):
108
+ text, ents = "", []
109
+ for seg, lab in segments:
110
+ if lab:
111
+ ents.append({"label": lab, "start": len(text), "end": len(text) + len(seg)})
112
+ text += seg
113
+ return {"text": text, "entities": ents}
114
+
115
+
116
+ # ======================= dataset 5 and model 9: document sensitivity tier =======================
117
+ THING = ["the Q3 roadmap", "the onboarding guide", "the vendor list", "the pricing sheet", "the incident review", "the hiring plan",
118
+ "the support playbook", "the launch plan", "the audit notes", "the budget draft", "the partner terms", "the training deck"]
119
+ TEAM = ["support", "finance", "engineering", "sales", "operations", "legal", "people team", "marketing"]
120
+ TIER_FILL = lambda: {"thing": rng.choice(THING), "team": rng.choice(TEAM), "org": org(), "n": rng.randint(3, 40), "month": rng.choice(MONTHS)}
121
+ TIER_TRAIN = {
122
+ "public": ["{org} announces {thing} in a press release for all media.", "Blog post for our website readers: how {team} works with customers.",
123
+ "Published job listing for a {team} role, visible on our careers page.", "Public FAQ answer about opening hours and how to contact {team}.",
124
+ "Product brochure PDF for download, approved by marketing for anyone.", "Social media post celebrating {n} years in business.",
125
+ "Open-source release notes for version {n}, posted on our public repository.", "Conference talk slides cleared for the public audience."],
126
+ "internal": ["Team update from {team}: {thing} moves to next sprint, share inside the company only.", "All-hands notes from {month}, for employees, not for customers.",
127
+ "Internal wiki page on how {team} files expense reports.", "Slack thread where {team} compares tools; staff only.",
128
+ "Meeting agenda for the weekly {team} sync and action items.", "Draft of {thing} circulating among managers for comment.",
129
+ "Staff newsletter with office closures and training dates.", "Org chart and desk map for employees."],
130
+ "confidential": ["Customer contract with {org} that names pricing, renewal terms and discounts.", "Revenue forecast for the board, not for release outside leadership.",
131
+ "Unreleased {thing}, under embargo until the announcement.", "Supplier agreement with negotiated rates for {team}.",
132
+ "Investor update with cash position and runway for {n} months.", "Source code of the billing service, access limited to engineers.",
133
+ "Acquisition discussion notes with {org}, known to a handful of executives.", "Salary bands and bonus plan for {team}, shared with HR only."],
134
+ "restricted": ["Patient records with names, diagnoses and insurance numbers, covered by health privacy law.", "Export of customer payment card numbers and billing addresses.",
135
+ "Employee Social Security numbers and bank details held by payroll.", "Production database credentials and private signing keys for {thing}.",
136
+ "Legal hold material under attorney-client privilege for a pending case.", "Unredacted identity documents scanned for {n} customers.",
137
+ "Full customer database with emails, phone numbers and home addresses, un-de-identified.", "Security incident forensics naming affected accounts and exploited weaknesses."],
138
+ }
139
+ TIER_TEST = {
140
+ "public": ["Newsletter issue for subscribers about {thing}, published openly.", "Open house invitation from {org}, posted for the neighbourhood."],
141
+ "internal": ["Reminder to {team} staff about the holiday schedule and who covers the desk.", "Notes from {team}'s retrospective on {thing}, inside the company."],
142
+ "confidential": ["Term sheet from {org}, only for the deal team.", "Customer churn analysis with account names and contract values."],
143
+ "restricted": ["Spreadsheet of staff medical leave reasons and doctors' notes.", "Raw customer list with government ID numbers attached to {n} rows."],
144
+ }
145
+ r = classification("document-sensitivity-tiers", "label", TIER_TRAIN, TIER_TEST, TIER_FILL, 10)
146
+ write("datasets", "synthetic-document-sensitivity-tiers", "train", with_ids("tier", [{"text": x["text"], "tier": x["label"]} for x in r["train"]]))
147
+ write("datasets", "synthetic-document-sensitivity-tiers", "test", with_ids("tier-t", [{"text": x["text"], "tier": x["label"]} for x in r["test"]]))
148
+ write("models", "document-sensitivity-tier-classifier", "train", r["train"])
149
+ write("models", "document-sensitivity-tier-classifier", "test", r["test"])
150
+
151
+ # ======================= dataset 6, models 4 and 10: contract clauses (type and risk) =======================
152
+ CL_FILL = lambda: {"a": rng.choice(["the Seller", "the Company", "Licensor"]), "b": rng.choice(["the Buyer", "the Lab", "Licensee"]),
153
+ "n": rng.choice([30, 45, 60, 90, 180]), "y": rng.choice([1, 2, 3, 5]), "p": rng.choice(["thirty", "forty-five", "sixty", "ninety"])}
154
+ CLAUSES = { # type -> (standard train, watch train, standard test, watch test)
155
+ "exclusivity": (["{b} receives exclusive rights to the Dataset for {n} days, after which {a} may license it to others.",
156
+ "During the exclusivity period of {n} days, {a} will not license the Dataset to any other party; the period ends on a fixed date."],
157
+ ["{b} receives exclusive rights to the Dataset for all purposes, with no end date.",
158
+ "{a} may never license the Dataset or anything derived from it to any other party, in any field."],
159
+ ["Exclusivity applies for {y} year(s) and only to the named use, and {a} keeps all other rights."],
160
+ ["The exclusive grant covers every future version of the Dataset and renews automatically without notice."]),
161
+ "term_and_renewal": (["This Agreement lasts {y} year(s) and renews only if both parties agree in writing.",
162
+ "The term is {y} year(s) from the effective date, and either party may decline renewal with {n} days' notice."],
163
+ ["This Agreement renews automatically for successive {y}-year terms and {a} cannot end it.",
164
+ "The term continues until {b} decides to stop, at its sole discretion."],
165
+ ["The initial term is {y} year(s); renewal needs a signed amendment."],
166
+ ["The license is perpetual and irrevocable and survives any breach by {b}."]),
167
+ "payment": (["{b} pays the fee within {n} days of an invoice, by bank transfer, and late amounts accrue modest interest.",
168
+ "The fee is due in stages: a deposit on signing and the balance on acceptance of the delivered data."],
169
+ ["{b} may withhold payment for any reason until it is fully satisfied, with no deadline.",
170
+ "Payment is due only after {b} has earned revenue from a model trained on the Dataset, and {a} cannot see those figures."],
171
+ ["Invoices are payable within {n} days and disputes must be raised in writing within ten days."],
172
+ ["Fees may be reduced by {b} at any time by notice, and refunds are due on request after delivery."]),
173
+ "confidentiality": (["Each party keeps the other's confidential information private for {y} year(s) after the Agreement ends, with the usual exceptions.",
174
+ "{b} will protect the Dataset with the care it gives its own secrets and limit access to those who need it."],
175
+ ["{a} must keep every detail of this Agreement secret forever, even from its own advisers and auditors.",
176
+ "{b} may disclose the Dataset to any third party without telling {a}."],
177
+ ["Confidential information excludes what is public, already known or independently developed."],
178
+ ["Confidentiality binds {a} only; {b} owes no duty of secrecy about anything it learns."]),
179
+ "ip_and_ownership": (["{a} keeps ownership of the Dataset and grants {b} a limited license for the stated purpose.",
180
+ "Models {b} trains remain {b}'s, and {a} keeps its rights in the source records."],
181
+ ["Ownership of the Dataset passes to {b} on delivery, including all future records {a} creates.",
182
+ "{b} owns everything {a} produces during the term, whether or not it relates to the Dataset."],
183
+ ["The license is non-transferable and covers only the field of use set out in the schedule."],
184
+ ["{a} assigns all present and future rights in its systems and records to {b} for no extra fee."]),
185
+ "indemnity": (["Each party defends the other against claims caused by its own breach, up to a stated cap.",
186
+ "{a} stands behind its statement that it has the right to license the Dataset, with liability capped at the fees paid."],
187
+ ["{a} indemnifies {b} without any cap for every claim of any kind, including claims {b} causes.",
188
+ "{a} pays {b}'s legal costs on any dispute, even where {a} wins."],
189
+ ["Liability under this clause is limited to the fees paid in the {n} days before the claim."],
190
+ ["{a} bears all liability for how {b} uses the Dataset after delivery, with no limit."]),
191
+ "audit_and_deletion": (["{a} may ask once a year for a written confirmation of how the Dataset is used, and {b} deletes it at the end of the term.",
192
+ "On request {b} confirms in writing that copies were deleted within {n} days."],
193
+ ["{b} may keep the Dataset forever and {a} has no way to check how it is used.",
194
+ "{b} may share the Dataset with its affiliates and partners and need not delete it."],
195
+ ["At the end of the term {b} deletes the Dataset and certifies it, and {a} may audit once with notice."],
196
+ ["There is no audit right and no duty to delete, even if {a} ends the Agreement for breach."]),
197
+ "termination": (["Either party may end the Agreement for uncured breach after {n} days' written notice.",
198
+ "{a} may end the Agreement if {b} misses a payment and does not fix it within {n} days."],
199
+ ["Only {b} may end the Agreement, at any time, and keeps everything delivered.",
200
+ "{a} cannot end the Agreement for any reason, including non-payment."],
201
+ ["Termination for convenience needs {n} days' notice, and fees already earned stay payable."],
202
+ ["On termination by {b}, all data already delivered stays with {b} and no fees are refunded or owed."]),
203
+ }
204
+ tr_t_type, te_t_type, tr_t_risk, te_t_risk = {}, {}, {"standard": [], "watch": []}, {"standard": [], "watch": []}
205
+ rows_train, rows_test = [], []
206
+ for ctype, (s_tr, w_tr, s_te, w_te) in CLAUSES.items():
207
+ for tpls, risk, rows in ((s_tr, "standard", rows_train), (w_tr, "watch", rows_train), (s_te, "standard", rows_test), (w_te, "watch", rows_test)):
208
+ seen = set()
209
+ for t in tpls:
210
+ for _ in range(12):
211
+ text = rng.choice(PRE) + t.format(**CL_FILL()) + rng.choice(SUF)
212
+ if text not in seen:
213
+ seen.add(text)
214
+ rows.append({"text": text, "clause_type": ctype, "risk": risk})
215
+ rng.shuffle(rows_train)
216
+ rng.shuffle(rows_test)
217
+ write("datasets", "synthetic-contract-clauses-by-type-and-risk", "train", with_ids("cl", rows_train))
218
+ write("datasets", "synthetic-contract-clauses-by-type-and-risk", "test", with_ids("cl-t", rows_test))
219
+ write("models", "contract-clause-type-classifier", "train", [{"text": x["text"], "label": x["clause_type"]} for x in rows_train])
220
+ write("models", "contract-clause-type-classifier", "test", [{"text": x["text"], "label": x["clause_type"]} for x in rows_test])
221
+ write("models", "contract-clause-risk-classifier", "train", [{"text": x["text"], "label": x["risk"]} for x in rows_train])
222
+ write("models", "contract-clause-risk-classifier", "test", [{"text": x["text"], "label": x["risk"]} for x in rows_test])
223
+
224
+ # ======================= dataset 7 and model 5: inbound inquiries by intent =======================
225
+ IQ_FILL = lambda: {"name": person(), "org": org(), "n": rng.choice([30, 45, 60, 120, 400]), "thing": rng.choice(
226
+ ["support tickets", "CRM history", "design files", "call recordings", "team chat", "project records", "code reviews", "invoices"])}
227
+ INTENT_TRAIN = {
228
+ "seller_interest": ["Hi, I run {org} with about {n} staff. We have years of {thing}. Could we sell or license it to an AI lab?",
229
+ "My company has a lot of {thing} sitting unused. How does licensing it work and what would you need from us?",
230
+ "We are closing down {org} and want to know whether our {thing} has any value to AI companies.",
231
+ "I saw your video. Does a firm of {n} people qualify, and who pays your fee?",
232
+ "Our owners are open to licensing {thing}. What is the first step?", "Interested in learning how to monetise our archive of {thing}. Please call me."],
233
+ "buyer_request": ["We are an AI lab looking for licensed {thing} from operating companies. Can you share what you have?",
234
+ "Our model team needs a dataset of {thing}, de-identified, with clear rights. Do you source this?",
235
+ "Looking to buy business records for training an agent. Which industries can you cover?",
236
+ "As a buyer we want sample data before committing. How do you handle diligence?",
237
+ "Please send what you can offer in {thing}; we have a budget this quarter.", "Our research group wants to license real-world {thing} and needs the rights confirmed."],
238
+ "referral_offer": ["I know several owners with {thing}. How does your referral arrangement work?",
239
+ "As a consultant I can introduce companies of {n} people. What do referrers receive?",
240
+ "My accounting clients keep asking about selling their data. Can we partner on introductions?",
241
+ "I run an M&A advisory and can bring wind-down companies. Do you work with referral partners?",
242
+ "Happy to introduce owners if the terms are fair. Please send your partner details.", "We have a list of companies that may want to license {thing}; can we share it with you?"],
243
+ "press_media": ["I am a reporter writing about AI training data. Could you give a comment on how licensing works?",
244
+ "Our podcast would like to interview you about selling company data to AI labs.",
245
+ "Writing a feature on data brokers for our newsletter; can we schedule a short call?",
246
+ "Journalist here. Do you have figures on how many companies license their data?",
247
+ "We would like to quote you in an article about {thing} and AI.", "An editor asked me to request an interview about your work."],
248
+ "vendor_pitch": ["We offer lead generation and can fill your calendar with {n} meetings a month.", "Our agency does SEO and link building for sites like yours. Want a free audit?",
249
+ "I can build you a website redesign at a discount this week.", "Our software helps companies like {org} automate outreach. Book a demo?",
250
+ "We sell guest posts on high authority blogs; interested?", "Special offer on email lists for your campaigns, limited time."],
251
+ "support_question": ["I filled in your form but did not get a reply. Can you check on it?", "How do I update the details I sent you last week?",
252
+ "I cannot open the calculator result page. Could you help?", "Where do I find the checklist you mentioned in the video?",
253
+ "I made an account, but I forgot which email I used. Can you help me sign in?", "The page says error when I submit my company information."],
254
+ "opt_out": ["Please remove me from your list.", "Stop emailing me. I am not interested.", "Unsubscribe me and do not contact this address again.",
255
+ "I did not ask for these messages. Take me off your list.", "Not interested, please delete my details.", "Remove {org} from your mailing list."],
256
+ "spam": ["Congratulations, you have won a prize, click here to claim.", "Buy cheap watches and pills now, best prices guaranteed.",
257
+ "Earn money fast from home, no skills needed, send your bank login.", "Your account is suspended, verify your password at the link.",
258
+ "Hot singles near you want to meet tonight.", "Crypto giveaway, send one coin and get ten back."],
259
+ }
260
+ INTENT_TEST = {
261
+ "seller_interest": ["We own a small agency, {n} employees, with {thing} going back years. Is there a market for this?", "Can {org} get paid for its {thing}? We would like an honest answer."],
262
+ "buyer_request": ["Procurement at our lab is exploring licensed {thing}; what is your sourcing process?", "We are building an evaluation set and need real {thing} with permission."],
263
+ "referral_offer": ["Do you pay a finder's fee if I connect you with a company of {n} staff?", "I advise owners on exits and would like to discuss working together on introductions."],
264
+ "press_media": ["I cover AI for a trade magazine and need an expert on data licensing for a quote.", "Would you join our webinar to explain how companies sell {thing} to AI labs?"],
265
+ "vendor_pitch": ["We help companies like {org} get more traffic with our paid promotion package.", "Our team can create videos for your channel at a fraction of the usual cost."],
266
+ "support_question": ["The link in your email does not work for me. Can you resend it?", "I need to correct a number I entered on the form. How do I do that?"],
267
+ "opt_out": ["Kindly take {org} off all further messages.", "I would like no further contact. Please confirm you removed me."],
268
+ "spam": ["Winner! Claim your free gift card by entering your card details here.", "Invest now and double your money in a week, guaranteed returns."],
269
+ }
270
+ r = classification("inquiries", "label", INTENT_TRAIN, INTENT_TEST, IQ_FILL, 10)
271
+ write("datasets", "synthetic-inbound-inquiries-by-intent", "train", with_ids("inq", [{"text": x["text"], "intent": x["label"]} for x in r["train"]]))
272
+ write("datasets", "synthetic-inbound-inquiries-by-intent", "test", with_ids("inq-t", [{"text": x["text"], "intent": x["label"]} for x in r["test"]]))
273
+ write("models", "inbound-inquiry-intent-classifier", "train", r["train"])
274
+ write("models", "inbound-inquiry-intent-classifier", "test", r["test"])
275
+
276
+ # ======================= dataset 8 and model 6: invoice and purchase order text with personal and account details =======================
277
+ def inv_fill():
278
+ return {"p": person(), "o": org(), "e": email(), "ph": phone(), "ad": address(), "d": date(), "m": money(), "i": code("INV-"), "ac": code("", 10), "t": code("", 9)}
279
+
280
+
281
+ def spans_template(parts):
282
+ """parts: list of (text-or-slot, label). Slot names in braces fill from `fill`."""
283
+ return parts
284
+
285
+
286
+ INV_TRAIN = [
287
+ [("Invoice ", None), ("{i}", "INVOICE_ID"), (" issued ", None), ("{d}", "DATE"), (" to ", None), ("{o}", "ORG"), (", attn ", None), ("{p}", "PERSON"), (". Amount due ", None), ("{m}", "MONEY"), (".", None)],
288
+ [("Bill to: ", None), ("{p}", "PERSON"), (", ", None), ("{ad}", "ADDRESS"), (". Contact ", None), ("{e}", "EMAIL"), (" or ", None), ("{ph}", "PHONE"), (". Total ", None), ("{m}", "MONEY"), (".", None)],
289
+ [("Please remit ", None), ("{m}", "MONEY"), (" to account ", None), ("{ac}", "BANK_ACCOUNT"), (" by ", None), ("{d}", "DATE"), (" quoting ", None), ("{i}", "INVOICE_ID"), (".", None)],
290
+ [("Vendor: ", None), ("{o}", "ORG"), (" (tax ID ", None), ("{t}", "TAX_ID"), ("). Remit to ", None), ("{ad}", "ADDRESS"), (".", None)],
291
+ [("Purchase order ", None), ("{i}", "INVOICE_ID"), (" approved by ", None), ("{p}", "PERSON"), (" on ", None), ("{d}", "DATE"), (" for ", None), ("{m}", "MONEY"), (".", None)],
292
+ [("Questions about this invoice? Email ", None), ("{e}", "EMAIL"), (" or call ", None), ("{ph}", "PHONE"), (". Ref ", None), ("{i}", "INVOICE_ID"), (".", None)],
293
+ [("Payment received from ", None), ("{o}", "ORG"), (" on ", None), ("{d}", "DATE"), (", ", None), ("{m}", "MONEY"), (", from account ending ", None), ("{ac}", "BANK_ACCOUNT"), (".", None)],
294
+ ]
295
+ INV_TEST = [
296
+ [("Statement for ", None), ("{p}", "PERSON"), (" at ", None), ("{o}", "ORG"), (": balance ", None), ("{m}", "MONEY"), (", due ", None), ("{d}", "DATE"), (".", None)],
297
+ [("Wire details: account ", None), ("{ac}", "BANK_ACCOUNT"), (", beneficiary ", None), ("{o}", "ORG"), (", tax number ", None), ("{t}", "TAX_ID"), (".", None)],
298
+ [("Ship to ", None), ("{ad}", "ADDRESS"), (" for ", None), ("{p}", "PERSON"), (", phone ", None), ("{ph}", "PHONE"), (", order ", None), ("{i}", "INVOICE_ID"), (".", None)],
299
+ [("Credit note ", None), ("{i}", "INVOICE_ID"), (" of ", None), ("{m}", "MONEY"), (" sent to ", None), ("{e}", "EMAIL"), (" on ", None), ("{d}", "DATE"), (".", None)],
300
+ ]
301
+
302
+
303
+ def render_spans(templates, per, fill):
304
+ rows, seen = [], set()
305
+ for tpl in templates:
306
+ for _ in range(per):
307
+ f = fill()
308
+ seg = []
309
+ for text, lab in tpl:
310
+ seg.append((text.format(**f) if "{" in text else text, lab))
311
+ row = spans_from(seg)
312
+ if row["text"] not in seen:
313
+ seen.add(row["text"])
314
+ rows.append(row)
315
+ rng.shuffle(rows)
316
+ return rows
317
+
318
+
319
+ tr, te = render_spans(INV_TRAIN, 40, inv_fill), render_spans(INV_TEST, 30, inv_fill)
320
+ write("datasets", "synthetic-invoice-pii-spans", "train", with_ids("inv", tr))
321
+ write("datasets", "synthetic-invoice-pii-spans", "test", with_ids("inv-t", te))
322
+ write("models", "invoice-pii-detector", "train", tr)
323
+ write("models", "invoice-pii-detector", "test", te)
324
+
325
+ # ======================= dataset 9 and model 7: chat logs with personal and technical identifiers =======================
326
+ def chat_fill():
327
+ return {"p": person(), "p2": person(), "e": email(), "ph": phone(), "k": key(), "ip": ip(), "t": code("TCK-", 5)}
328
+
329
+
330
+ CHAT_TRAIN = [
331
+ [("[10:42] ", None), ("{p}", "PERSON"), (": can you email the file to ", None), ("{e}", "EMAIL"), (" before lunch?", None)],
332
+ [("[14:03] ", None), ("{p}", "PERSON"), (": customer rang from ", None), ("{ph}", "PHONE"), (", ticket is ", None), ("{t}", "ID_NUMBER"), (".", None)],
333
+ [("oops I pasted the key in here: ", None), ("{k}", "API_KEY"), (" please rotate it", None)],
334
+ [("{p}", "PERSON"), (" > the server at ", None), ("{ip}", "IP_ADDRESS"), (" keeps timing out again", None)],
335
+ [("@", None), ("{p2}", "PERSON"), (" can you take ", None), ("{t}", "ID_NUMBER"), ("? ", None), ("{p}", "PERSON"), (" is out today", None)],
336
+ [("Reach me on ", None), ("{ph}", "PHONE"), (" if the deploy fails, or ", None), ("{e}", "EMAIL"), (".", None)],
337
+ [("config line: token=", None), ("{k}", "API_KEY"), (" host=", None), ("{ip}", "IP_ADDRESS")],
338
+ [("thanks ", None), ("{p2}", "PERSON"), (", closing ", None), ("{t}", "ID_NUMBER"), (" now", None)],
339
+ ]
340
+ CHAT_TEST = [
341
+ [("DM from ", None), ("{p}", "PERSON"), (": my login is ", None), ("{e}", "EMAIL"), (", the old box is ", None), ("{ip}", "IP_ADDRESS")],
342
+ [("Heads up ", None), ("{p2}", "PERSON"), (": api secret ", None), ("{k}", "API_KEY"), (" is in the wiki page, remove it", None)],
343
+ [("call ", None), ("{p}", "PERSON"), (" on ", None), ("{ph}", "PHONE"), (" about ", None), ("{t}", "ID_NUMBER")],
344
+ [("{p}", "PERSON"), (" and ", None), ("{p2}", "PERSON"), (" are both cc'd at ", None), ("{e}", "EMAIL")],
345
+ ]
346
+ tr, te = render_spans(CHAT_TRAIN, 45, chat_fill), render_spans(CHAT_TEST, 35, chat_fill)
347
+ write("datasets", "synthetic-chat-logs-pii-spans", "train", with_ids("chat", tr))
348
+ write("datasets", "synthetic-chat-logs-pii-spans", "test", with_ids("chat-t", te))
349
+ write("models", "chat-log-pii-detector", "train", tr)
350
+ write("models", "chat-log-pii-detector", "test", te)
351
+
352
+ # ======================= dataset 10 and model 8: sales call notes by outcome =======================
353
+ CALL_FILL = lambda: {"name": person(), "org": org(), "n": rng.choice([2, 3, 4, 6]), "w": rng.choice(["Friday", "next Tuesday", "the 14th", "end of month"]),
354
+ "thing": rng.choice(["the pilot", "the proposal", "the quote", "the demo", "the sample"])}
355
+ OUT_TRAIN = {
356
+ "advanced": ["Spoke with {name} at {org}. Liked {thing}, wants a second call with the team on {w}.", "{name} asked for a written proposal and named two colleagues to join the next meeting.",
357
+ "Good fit confirmed. {org} will send access for {thing} and we booked a review on {w}.", "Budget approved in principle by {name}; next step is the security questionnaire.",
358
+ "{name} introduced us to legal at {org}. Drafting terms this week.", "Strong interest. They want {thing} reworked for their data and will reply by {w}."],
359
+ "stalled": ["{name} said the team is busy and to check back after {w}. No date set.", "Waiting on {org} to reply to the last {n} emails. Nothing since the call.",
360
+ "{name} likes it but needs sign-off from a person they cannot name yet.", "Pushed back to next quarter by {org}; no owner for {thing}.",
361
+ "Left a voicemail twice for {name}. Still no answer on {thing}.", "Interest is real but their priorities changed and the project is on hold."],
362
+ "lost": ["{name} told us {org} chose another provider and will not continue.", "Price was too high for {org}; {name} closed the file.",
363
+ "{org} decided to build it in house, so no further calls are needed.", "{name} said it is not a fit for their size and asked us to stop following up.",
364
+ "Legal at {org} refused the data terms and the deal ended.", "{name} replied that they have no interest in {thing} and no budget now or later."],
365
+ "won": ["{name} signed the agreement for {org} and we start on {w}.", "Contract returned countersigned by {org}; invoice sent for {thing}.",
366
+ "{org} confirmed the order after {thing} and payment is scheduled for {w}.", "{name} approved the final terms; kickoff booked with {n} people from their side.",
367
+ "Deal closed. {org} signed, and first delivery is planned for {w}.", "Signed today. {name} thanked the team and asked for the onboarding pack."],
368
+ }
369
+ OUT_TEST = {
370
+ "advanced": ["{name} wants {thing} sent to their technical lead before the next call with {org}.", "They asked for references and set a follow-up for {w}."],
371
+ "stalled": ["No response from {org} after the proposal; last contact was {n} weeks ago.", "{name} is travelling and said to try again later without giving a date."],
372
+ "lost": ["{org} went with a cheaper alternative, according to {name}.", "{name} said the project was cancelled and {org} no longer needs this."],
373
+ "won": ["{org} has accepted the quote and {name} sent the purchase order.", "We got the yes from {name}; paperwork is complete."],
374
+ }
375
+ r = classification("calls", "label", OUT_TRAIN, OUT_TEST, CALL_FILL, 10)
376
+ write("datasets", "synthetic-sales-call-notes-by-outcome", "train", with_ids("call", [{"text": x["text"], "outcome": x["label"]} for x in r["train"]]))
377
+ write("datasets", "synthetic-sales-call-notes-by-outcome", "test", with_ids("call-t", [{"text": x["text"], "outcome": x["label"]} for x in r["test"]]))
378
+ write("models", "sales-call-outcome-classifier", "train", r["train"])
379
+ write("models", "sales-call-outcome-classifier", "test", r["test"])