1pub struct KindSpec {
8 pub kind: &'static str,
9 pub prefix: &'static str,
10 pub roles: (&'static str, &'static str),
12 pub description: &'static str,
13 pub tags: &'static [&'static str],
14}
15
16pub const KINDS: &[KindSpec] = &[
17 KindSpec {
18 kind: "nda",
19 prefix: "NDA",
20 roles: ("Disclosing Party", "Receiving Party"),
21 description: "Confidentiality agreement (mutual or one-way) — protects information exchanged while exploring a deal.",
22 tags: &[
23 "confidential", "confidentiality", "secret", "nondisclosure", "non-disclosure",
24 "disclosure", "privacy", "protect", "information", "evaluate", "exploring",
25 ],
26 },
27 KindSpec {
28 kind: "ncnda",
29 prefix: "NCNDA",
30 roles: ("Party", "Party"),
31 description: "Non-circumvention + non-disclosure — stops a counterparty going around you to deal directly with contacts you introduce, and protects the information shared.",
32 tags: &[
33 "circumvent", "circumvention", "non-circumvention", "go-around", "bypass",
34 "introducer", "introduction", "intermediary", "broker", "middleman",
35 "commission", "protect-contacts", "steal", "poach", "direct-deal", "cut-out",
36 ],
37 },
38 KindSpec {
39 kind: "consulting",
40 prefix: "CTR",
41 roles: ("Consultant", "Client"),
42 description: "One engagement with defined deliverables and a fee — a self-contained services contract.",
43 tags: &[
44 "freelance", "project", "deliverables", "engagement", "gig", "advisory",
45 "consultant", "services", "scope", "fee",
46 ],
47 },
48 KindSpec {
49 kind: "msa",
50 prefix: "MSA",
51 roles: ("Provider", "Customer"),
52 description: "Master services agreement — umbrella terms with no fee or scope; SOWs hang off it per piece of work.",
53 tags: &[
54 "master", "framework", "umbrella", "long-term", "terms", "relationship",
55 ],
56 },
57 KindSpec {
58 kind: "sow",
59 prefix: "SOW",
60 roles: ("Provider", "Customer"),
61 description: "Statement of work under an MSA — scope, deliverables, milestones, and price for one project.",
62 tags: &[
63 "scope", "milestones", "work-order", "statement", "under-msa", "project",
64 ],
65 },
66 KindSpec {
67 kind: "service",
68 prefix: "SVC",
69 roles: ("Provider", "Customer"),
70 description: "Ongoing or recurring services — retainers, hosting, support, monthly reviews.",
71 tags: &[
72 "retainer", "ongoing", "recurring", "monthly", "hosting", "support",
73 "subscription", "maintenance", "managed",
74 ],
75 },
76 KindSpec {
77 kind: "loan",
78 prefix: "LOAN",
79 roles: ("Lender", "Borrower"),
80 description: "Loan agreement between two parties — principal, interest (or interest-free), repayment date, default, and optional security.",
81 tags: &[
82 "loan", "lend", "lending", "borrow", "money", "principal", "interest",
83 "repayment", "repay", "credit", "advance", "bridge", "facility",
84 ],
85 },
86];
87
88pub fn get(kind: &str) -> Option<&'static KindSpec> {
89 KINDS.iter().find(|k| k.kind == kind)
90}
91
92pub fn names() -> Vec<&'static str> {
93 KINDS.iter().map(|k| k.kind).collect()
94}
95
96pub fn prefix_for(kind: &str) -> &'static str {
97 get(kind).map(|k| k.prefix).unwrap_or("DOC")
98}
99
100pub fn score(query: &str, description: &str, tags: &[&str]) -> f64 {
103 let q = query.to_lowercase();
104 let q_tokens: Vec<&str> = q
105 .split(|c: char| !c.is_alphanumeric() && c != '-')
106 .filter(|t| t.len() > 2)
107 .collect();
108 if q_tokens.is_empty() {
109 return 0.0;
110 }
111 let desc = description.to_lowercase();
112 let mut hits = 0.0;
113 for t in &q_tokens {
114 if tags.iter().any(|tag| tag == t) {
116 hits += 3.0;
117 } else if tags.iter().any(|tag| tag.starts_with(*t) || t.starts_with(*tag)) {
118 hits += 1.5;
119 } else if desc.contains(*t) {
120 hits += 1.0;
121 }
122 }
123 hits / q_tokens.len() as f64
124}