synkro 0.4.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. synkro/__init__.py +179 -0
  2. synkro/advanced.py +186 -0
  3. synkro/cli.py +128 -0
  4. synkro/core/__init__.py +7 -0
  5. synkro/core/checkpoint.py +250 -0
  6. synkro/core/dataset.py +402 -0
  7. synkro/core/policy.py +337 -0
  8. synkro/errors.py +178 -0
  9. synkro/examples/__init__.py +148 -0
  10. synkro/factory.py +276 -0
  11. synkro/formatters/__init__.py +12 -0
  12. synkro/formatters/qa.py +98 -0
  13. synkro/formatters/sft.py +90 -0
  14. synkro/formatters/tool_call.py +127 -0
  15. synkro/generation/__init__.py +9 -0
  16. synkro/generation/follow_ups.py +134 -0
  17. synkro/generation/generator.py +220 -0
  18. synkro/generation/golden_responses.py +244 -0
  19. synkro/generation/golden_scenarios.py +276 -0
  20. synkro/generation/golden_tool_responses.py +416 -0
  21. synkro/generation/logic_extractor.py +126 -0
  22. synkro/generation/multiturn_responses.py +177 -0
  23. synkro/generation/planner.py +131 -0
  24. synkro/generation/responses.py +189 -0
  25. synkro/generation/scenarios.py +90 -0
  26. synkro/generation/tool_responses.py +376 -0
  27. synkro/generation/tool_simulator.py +114 -0
  28. synkro/interactive/__init__.py +12 -0
  29. synkro/interactive/hitl_session.py +77 -0
  30. synkro/interactive/logic_map_editor.py +173 -0
  31. synkro/interactive/rich_ui.py +205 -0
  32. synkro/llm/__init__.py +7 -0
  33. synkro/llm/client.py +235 -0
  34. synkro/llm/rate_limits.py +95 -0
  35. synkro/models/__init__.py +43 -0
  36. synkro/models/anthropic.py +26 -0
  37. synkro/models/google.py +19 -0
  38. synkro/models/openai.py +31 -0
  39. synkro/modes/__init__.py +15 -0
  40. synkro/modes/config.py +66 -0
  41. synkro/modes/qa.py +18 -0
  42. synkro/modes/sft.py +18 -0
  43. synkro/modes/tool_call.py +18 -0
  44. synkro/parsers.py +442 -0
  45. synkro/pipeline/__init__.py +20 -0
  46. synkro/pipeline/phases.py +592 -0
  47. synkro/pipeline/runner.py +424 -0
  48. synkro/pipelines.py +123 -0
  49. synkro/prompts/__init__.py +57 -0
  50. synkro/prompts/base.py +167 -0
  51. synkro/prompts/golden_templates.py +474 -0
  52. synkro/prompts/interactive_templates.py +65 -0
  53. synkro/prompts/multiturn_templates.py +156 -0
  54. synkro/prompts/qa_templates.py +97 -0
  55. synkro/prompts/templates.py +281 -0
  56. synkro/prompts/tool_templates.py +201 -0
  57. synkro/quality/__init__.py +14 -0
  58. synkro/quality/golden_refiner.py +163 -0
  59. synkro/quality/grader.py +153 -0
  60. synkro/quality/multiturn_grader.py +150 -0
  61. synkro/quality/refiner.py +137 -0
  62. synkro/quality/tool_grader.py +126 -0
  63. synkro/quality/tool_refiner.py +128 -0
  64. synkro/quality/verifier.py +228 -0
  65. synkro/reporting.py +537 -0
  66. synkro/schemas.py +472 -0
  67. synkro/types/__init__.py +41 -0
  68. synkro/types/core.py +126 -0
  69. synkro/types/dataset_type.py +30 -0
  70. synkro/types/logic_map.py +345 -0
  71. synkro/types/tool.py +94 -0
  72. synkro-0.4.12.data/data/examples/__init__.py +148 -0
  73. synkro-0.4.12.dist-info/METADATA +258 -0
  74. synkro-0.4.12.dist-info/RECORD +77 -0
  75. synkro-0.4.12.dist-info/WHEEL +4 -0
  76. synkro-0.4.12.dist-info/entry_points.txt +2 -0
  77. synkro-0.4.12.dist-info/licenses/LICENSE +21 -0
synkro/core/policy.py ADDED
@@ -0,0 +1,337 @@
1
+ """Policy document handling with multi-format support."""
2
+
3
+ from pathlib import Path
4
+ from typing import Sequence
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from synkro.errors import FileNotFoundError as SynkroFileNotFoundError, PolicyTooShortError
9
+
10
+
11
+ MIN_POLICY_WORDS = 10 # Minimum words for meaningful generation
12
+
13
+ # Supported file extensions
14
+ SUPPORTED_EXTENSIONS = {".txt", ".md", ".pdf", ".docx"}
15
+
16
+
17
+ class Policy(BaseModel):
18
+ """
19
+ A policy document to generate training data from.
20
+
21
+ Supports loading from multiple formats:
22
+ - Plain text (.txt, .md)
23
+ - PDF documents (.pdf) - via marker-pdf
24
+ - Word documents (.docx) - via mammoth
25
+
26
+ Can load from:
27
+ - Single file: Policy.from_file("compliance.pdf")
28
+ - Folder of files: Policy.from_file("policies/")
29
+ - Multiple files: Policy.from_files(["doc1.pdf", "doc2.docx"])
30
+ - URL: Policy.from_url("https://example.com/policy")
31
+
32
+ Examples:
33
+ >>> # From text
34
+ >>> policy = Policy(text="All expenses over $50 require approval")
35
+
36
+ >>> # From single file
37
+ >>> policy = Policy.from_file("compliance.pdf")
38
+
39
+ >>> # From folder (loads all supported files)
40
+ >>> policy = Policy.from_file("policies/")
41
+
42
+ >>> # From multiple files
43
+ >>> policy = Policy.from_files(["doc1.pdf", "doc2.docx", "doc3.txt"])
44
+
45
+ >>> # From URL
46
+ >>> policy = Policy.from_url("https://example.com/policy")
47
+ """
48
+
49
+ text: str = Field(description="Full policy text in markdown format")
50
+ source: str | None = Field(default=None, description="Source file path or URL")
51
+
52
+ @classmethod
53
+ def from_file(cls, path: str | Path) -> "Policy":
54
+ """
55
+ Load policy from a file or folder.
56
+
57
+ Supports: .txt, .md, .pdf, .docx
58
+
59
+ If path is a directory, loads all supported files from that directory.
60
+ If path is a file, loads that single file.
61
+
62
+ Args:
63
+ path: Path to the policy file or folder containing policy files
64
+
65
+ Returns:
66
+ Policy object with extracted text (combined if multiple files)
67
+
68
+ Examples:
69
+ >>> # Single file
70
+ >>> policy = Policy.from_file("compliance.pdf")
71
+ >>> len(policy.text) > 0
72
+ True
73
+
74
+ >>> # Folder of documents
75
+ >>> policy = Policy.from_file("policies/")
76
+ >>> len(policy.text) > 0
77
+ True
78
+ """
79
+ path = Path(path)
80
+
81
+ if not path.exists():
82
+ # Find similar files to suggest
83
+ similar = []
84
+ if path.parent.exists():
85
+ for ext in SUPPORTED_EXTENSIONS:
86
+ similar.extend(path.parent.glob(f"*{ext}"))
87
+ similar_names = [str(f.name) for f in similar[:5]]
88
+ raise SynkroFileNotFoundError(str(path), similar_names if similar_names else None)
89
+
90
+ # If it's a directory, load all supported files
91
+ if path.is_dir():
92
+ return cls._from_folder(path)
93
+
94
+ # Otherwise, load single file
95
+ return cls._load_single_file(path)
96
+
97
+ @classmethod
98
+ def _load_single_file(cls, path: Path) -> "Policy":
99
+ """
100
+ Load a single policy file.
101
+
102
+ Args:
103
+ path: Path to a single file
104
+
105
+ Returns:
106
+ Policy object with extracted text
107
+ """
108
+ suffix = path.suffix.lower()
109
+
110
+ if suffix in (".txt", ".md"):
111
+ return cls(text=path.read_text(), source=str(path))
112
+
113
+ if suffix == ".pdf":
114
+ return cls._from_pdf(path)
115
+
116
+ if suffix == ".docx":
117
+ return cls._from_docx(path)
118
+
119
+ raise ValueError(f"Unsupported file type: {suffix}. Use .txt, .md, .pdf, or .docx")
120
+
121
+ @classmethod
122
+ def _from_folder(cls, folder_path: Path) -> "Policy":
123
+ """
124
+ Load all supported files from a folder.
125
+
126
+ Args:
127
+ folder_path: Path to folder containing policy files
128
+
129
+ Returns:
130
+ Policy object with combined text from all files
131
+
132
+ Raises:
133
+ ValueError: If no supported files found in folder
134
+ """
135
+ # Find all supported files in folder (non-recursive)
136
+ files = [
137
+ f for f in folder_path.iterdir()
138
+ if f.is_file() and f.suffix.lower() in SUPPORTED_EXTENSIONS
139
+ ]
140
+
141
+ if not files:
142
+ raise ValueError(
143
+ f"No supported policy files found in {folder_path}. "
144
+ f"Supported formats: {', '.join(SUPPORTED_EXTENSIONS)}"
145
+ )
146
+
147
+ # Sort files for consistent ordering
148
+ files.sort(key=lambda f: f.name)
149
+
150
+ # Load each file and combine
151
+ return cls.from_files(files, source_prefix=str(folder_path))
152
+
153
+ @classmethod
154
+ def from_files(
155
+ cls,
156
+ paths: Sequence[str | Path],
157
+ separator: str = "\n\n---\n\n",
158
+ source_prefix: str | None = None,
159
+ ) -> "Policy":
160
+ """
161
+ Load and combine multiple policy documents.
162
+
163
+ Args:
164
+ paths: List of paths to policy files
165
+ separator: String to separate documents (default: "\\n\\n---\\n\\n")
166
+ source_prefix: Optional prefix for source description (e.g., folder path)
167
+
168
+ Returns:
169
+ Policy object with combined text from all files
170
+
171
+ Raises:
172
+ ValueError: If paths list is empty
173
+ FileNotFoundError: If any file doesn't exist
174
+
175
+ Examples:
176
+ >>> # Multiple files
177
+ >>> policy = Policy.from_files(["doc1.pdf", "doc2.docx", "doc3.txt"])
178
+ >>> len(policy.text) > 0
179
+ True
180
+
181
+ >>> # With custom separator
182
+ >>> policy = Policy.from_files(
183
+ ... ["part1.txt", "part2.txt"],
184
+ ... separator="\\n\\n=== NEXT DOCUMENT ===\\n\\n"
185
+ ... )
186
+ """
187
+ if not paths:
188
+ raise ValueError("paths list cannot be empty")
189
+
190
+ texts: list[str] = []
191
+ sources: list[str] = []
192
+
193
+ for path in paths:
194
+ path_obj = Path(path)
195
+
196
+ if not path_obj.exists():
197
+ raise SynkroFileNotFoundError(str(path_obj), None)
198
+
199
+ if not path_obj.is_file():
200
+ raise ValueError(f"Path is not a file: {path_obj}")
201
+
202
+ # Load the file
203
+ policy = cls._load_single_file(path_obj)
204
+ texts.append(policy.text)
205
+ sources.append(str(path_obj))
206
+
207
+ # Combine texts
208
+ combined_text = separator.join(texts)
209
+
210
+ # Create source description
211
+ if source_prefix:
212
+ combined_source = f"{source_prefix} ({len(sources)} files: {', '.join(Path(s).name for s in sources)})"
213
+ else:
214
+ combined_source = f"multiple_files ({len(sources)} files: {', '.join(Path(s).name for s in sources)})"
215
+
216
+ return cls(text=combined_text, source=combined_source)
217
+
218
+ @classmethod
219
+ def _from_pdf(cls, path: Path) -> "Policy":
220
+ """
221
+ Parse PDF to markdown using marker-pdf.
222
+
223
+ Args:
224
+ path: Path to PDF file
225
+
226
+ Returns:
227
+ Policy with extracted markdown text
228
+ """
229
+ try:
230
+ from marker.convert import convert_single_pdf
231
+ from marker.models import load_all_models
232
+
233
+ models = load_all_models()
234
+ markdown, _, _ = convert_single_pdf(str(path), models)
235
+ return cls(text=markdown, source=str(path))
236
+ except ImportError:
237
+ raise ImportError(
238
+ "marker-pdf is required for PDF support. "
239
+ "Install with: pip install marker-pdf"
240
+ )
241
+
242
+ @classmethod
243
+ def _from_docx(cls, path: Path) -> "Policy":
244
+ """
245
+ Parse DOCX to markdown using mammoth.
246
+
247
+ Args:
248
+ path: Path to DOCX file
249
+
250
+ Returns:
251
+ Policy with extracted markdown text
252
+ """
253
+ try:
254
+ import mammoth
255
+
256
+ with open(path, "rb") as f:
257
+ result = mammoth.convert_to_markdown(f)
258
+ return cls(text=result.value, source=str(path))
259
+ except ImportError:
260
+ raise ImportError(
261
+ "mammoth is required for DOCX support. "
262
+ "Install with: pip install mammoth"
263
+ )
264
+
265
+ @classmethod
266
+ def from_url(cls, url: str) -> "Policy":
267
+ """
268
+ Fetch and parse policy from a URL.
269
+
270
+ Extracts main content and converts to markdown.
271
+
272
+ Args:
273
+ url: URL to fetch
274
+
275
+ Returns:
276
+ Policy with extracted content
277
+
278
+ Example:
279
+ >>> policy = Policy.from_url("https://example.com/terms")
280
+ """
281
+ try:
282
+ import httpx
283
+ from bs4 import BeautifulSoup
284
+ import html2text
285
+
286
+ response = httpx.get(url, follow_redirects=True)
287
+ response.raise_for_status()
288
+
289
+ soup = BeautifulSoup(response.text, "html.parser")
290
+
291
+ # Remove scripts, styles, nav, footer
292
+ for tag in soup(["script", "style", "nav", "footer", "header"]):
293
+ tag.decompose()
294
+
295
+ # Convert to markdown
296
+ h = html2text.HTML2Text()
297
+ h.ignore_links = False
298
+ h.ignore_images = True
299
+ markdown = h.handle(str(soup))
300
+
301
+ return cls(text=markdown, source=url)
302
+ except ImportError as e:
303
+ missing = str(e).split("'")[1] if "'" in str(e) else "required packages"
304
+ raise ImportError(
305
+ f"{missing} is required for URL support. "
306
+ "This should be installed automatically with synkro. "
307
+ "If you see this error, try: pip install --upgrade synkro"
308
+ )
309
+
310
+ @property
311
+ def word_count(self) -> int:
312
+ """Get the word count of the policy."""
313
+ return len(self.text.split())
314
+
315
+ @property
316
+ def char_count(self) -> int:
317
+ """Get the character count of the policy."""
318
+ return len(self.text)
319
+
320
+ def validate_length(self) -> None:
321
+ """
322
+ Validate that the policy has enough content for meaningful generation.
323
+
324
+ Raises:
325
+ PolicyTooShortError: If policy is too short
326
+ """
327
+ if self.word_count < MIN_POLICY_WORDS:
328
+ raise PolicyTooShortError(self.word_count)
329
+
330
+ def __str__(self) -> str:
331
+ """String representation showing source and length."""
332
+ source = self.source or "inline"
333
+ return f"Policy(source={source}, words={self.word_count})"
334
+
335
+ def __repr__(self) -> str:
336
+ return self.__str__()
337
+
synkro/errors.py ADDED
@@ -0,0 +1,178 @@
1
+ """Friendly error messages with fix suggestions."""
2
+
3
+ from rich.console import Console
4
+ from rich.panel import Panel
5
+
6
+ console = Console()
7
+
8
+
9
+ class SynkroError(Exception):
10
+ """Base exception for Synkro errors."""
11
+
12
+ def __init__(self, message: str, suggestion: str = ""):
13
+ self.message = message
14
+ self.suggestion = suggestion
15
+ super().__init__(message)
16
+
17
+ def print_friendly(self):
18
+ """Print a user-friendly error message."""
19
+ content = f"[red bold]{self.message}[/red bold]"
20
+ if self.suggestion:
21
+ content += f"\n\n[dim]{self.suggestion}[/dim]"
22
+ console.print(Panel(content, title="[red]Error[/red]", border_style="red"))
23
+
24
+
25
+ class APIKeyError(SynkroError):
26
+ """Raised when API key is missing or invalid."""
27
+
28
+ def __init__(self, provider: str = "OpenAI"):
29
+ provider_info = {
30
+ "openai": {
31
+ "env_var": "OPENAI_API_KEY",
32
+ "url": "https://platform.openai.com/api-keys",
33
+ },
34
+ "anthropic": {
35
+ "env_var": "ANTHROPIC_API_KEY",
36
+ "url": "https://console.anthropic.com/settings/keys",
37
+ },
38
+ "google": {
39
+ "env_var": "GEMINI_API_KEY",
40
+ "url": "https://aistudio.google.com/app/apikey",
41
+ },
42
+ }
43
+
44
+ info = provider_info.get(provider.lower(), provider_info["openai"])
45
+
46
+ message = f"{provider} API key not found or invalid"
47
+ suggestion = f"""To fix this, either:
48
+
49
+ 1. Set environment variable:
50
+ export {info['env_var']}="your-key-here"
51
+
52
+ 2. Pass directly:
53
+ synkro.generate(..., generation_model=LLM(api_key="..."))
54
+
55
+ Get an API key at: {info['url']}"""
56
+
57
+ super().__init__(message, suggestion)
58
+
59
+
60
+ class FileNotFoundError(SynkroError):
61
+ """Raised when a policy file cannot be found."""
62
+
63
+ def __init__(self, filepath: str, similar_files: list[str] | None = None):
64
+ message = f"Could not find file: {filepath}"
65
+
66
+ suggestion_parts = []
67
+ if similar_files:
68
+ suggestion_parts.append("Did you mean one of these?")
69
+ for f in similar_files[:3]:
70
+ suggestion_parts.append(f" → {f}")
71
+ suggestion_parts.append("")
72
+
73
+ suggestion_parts.append("Or pass text directly:")
74
+ suggestion_parts.append(' synkro.generate("Your policy text here...")')
75
+
76
+ super().__init__(message, "\n".join(suggestion_parts))
77
+
78
+
79
+ class RateLimitError(SynkroError):
80
+ """Raised when hitting API rate limits."""
81
+
82
+ def __init__(self, provider: str = "OpenAI", retry_after: int | None = None):
83
+ message = f"Rate limited by {provider}"
84
+ if retry_after:
85
+ message += f" (retry after {retry_after}s)"
86
+
87
+ suggestion = """Tip: Reduce the number of traces or wait and retry:
88
+
89
+ synkro.generate(..., traces=10)
90
+
91
+ Or use a different provider:
92
+ synkro.generate(..., generation_model=Google.GEMINI_25_FLASH)"""
93
+
94
+ super().__init__(message, suggestion)
95
+
96
+
97
+ class PolicyTooShortError(SynkroError):
98
+ """Raised when policy text is too short to generate meaningful data."""
99
+
100
+ def __init__(self, word_count: int):
101
+ message = f"Policy too short ({word_count} words)"
102
+ suggestion = """Your policy needs more content to generate diverse training data.
103
+
104
+ Minimum recommended: 50+ words with clear rules or guidelines.
105
+
106
+ Example:
107
+ synkro.generate('''
108
+ All expenses over $50 require manager approval.
109
+ Expenses over $500 require VP approval.
110
+ Receipts are required for all purchases over $25.
111
+ ''')"""
112
+
113
+ super().__init__(message, suggestion)
114
+
115
+
116
+ class ModelNotFoundError(SynkroError):
117
+ """Raised when specified model doesn't exist."""
118
+
119
+ def __init__(self, model: str):
120
+ message = f"Model not found: {model}"
121
+ suggestion = """Available models:
122
+
123
+ OpenAI: OpenAI.GPT_4O_MINI, OpenAI.GPT_4O
124
+ Anthropic: Anthropic.CLAUDE_35_SONNET, Anthropic.CLAUDE_35_HAIKU
125
+ Google: Google.GEMINI_25_FLASH, Google.GEMINI_25_PRO
126
+
127
+ Or pass any model string: "gpt-4o-mini", "claude-3-5-sonnet-20241022", etc."""
128
+
129
+ super().__init__(message, suggestion)
130
+
131
+
132
+ def _detect_provider(error_str: str) -> str:
133
+ """Detect the provider from the error message."""
134
+ error_lower = error_str.lower()
135
+ if "gemini" in error_lower or "googleapis" in error_lower or "google" in error_lower:
136
+ return "Google"
137
+ if "anthropic" in error_lower or "claude" in error_lower:
138
+ return "Anthropic"
139
+ return "OpenAI"
140
+
141
+
142
+ def handle_error(func):
143
+ """Decorator to catch and display friendly errors."""
144
+ import sys
145
+
146
+ def wrapper(*args, **kwargs):
147
+ try:
148
+ return func(*args, **kwargs)
149
+ except SynkroError as e:
150
+ e.print_friendly()
151
+ sys.exit(1)
152
+ except Exception as e:
153
+ # Try to convert common errors to friendly ones
154
+ error_str = str(e)
155
+ error_lower = error_str.lower()
156
+
157
+ if "api key" in error_lower or "authentication" in error_lower or "unauthorized" in error_lower:
158
+ provider = _detect_provider(error_str)
159
+ friendly = APIKeyError(provider)
160
+ friendly.print_friendly()
161
+ sys.exit(1)
162
+
163
+ if "rate limit" in error_lower or "429" in error_lower:
164
+ provider = _detect_provider(error_str)
165
+ friendly = RateLimitError(provider)
166
+ friendly.print_friendly()
167
+ sys.exit(1)
168
+
169
+ if "not found" in error_lower and "model" in error_lower:
170
+ friendly = ModelNotFoundError(str(e))
171
+ friendly.print_friendly()
172
+ sys.exit(1)
173
+
174
+ # Re-raise unknown errors
175
+ raise
176
+
177
+ return wrapper
178
+
@@ -0,0 +1,148 @@
1
+ """Built-in example policies for instant demos."""
2
+
3
+ EXPENSE_POLICY = """# Company Expense Policy
4
+
5
+ ## Approval Thresholds
6
+ - Expenses under $50: No approval required
7
+ - Expenses $50-$500: Manager approval required
8
+ - Expenses over $500: VP approval required
9
+
10
+ ## Receipt Requirements
11
+ - All expenses over $25 must have a receipt
12
+ - Digital receipts are acceptable
13
+ - Missing receipts require written justification within 48 hours
14
+
15
+ ## Categories
16
+ - Travel: Flights, hotels, ground transportation, meals while traveling
17
+ - Meals: Client meals, team events (max $75/person)
18
+ - Software: Must be on pre-approved list, exceptions need IT approval
19
+ - Equipment: Must be on asset tracking list if over $200
20
+ - Office Supplies: Under $100 can be purchased directly
21
+
22
+ ## Reimbursement Timeline
23
+ - Submit expenses within 30 days of purchase
24
+ - Reimbursements processed within 14 business days
25
+ - Late submissions require manager exception approval
26
+ """
27
+
28
+ HR_HANDBOOK = """# Employee Handbook
29
+
30
+ ## Work Hours
31
+ - Standard work week is 40 hours, Monday through Friday
32
+ - Core hours are 10am to 3pm when all employees should be available
33
+ - Flexible scheduling allowed with manager approval
34
+
35
+ ## Time Off
36
+ - Full-time employees receive 15 days PTO per year
37
+ - PTO accrues monthly (1.25 days per month)
38
+ - Unused PTO can roll over up to 5 days
39
+ - PTO requests must be submitted 2 weeks in advance for 3+ days
40
+
41
+ ## Remote Work
42
+ - Hybrid schedule: minimum 2 days in office per week
43
+ - Fully remote requires director approval
44
+ - Home office stipend of $500 for remote workers
45
+
46
+ ## Performance Reviews
47
+ - Annual reviews conducted in December
48
+ - Mid-year check-ins in June
49
+ - Goals set at start of fiscal year
50
+ - Promotions considered during annual review cycle only
51
+ """
52
+
53
+ REFUND_POLICY = """# Return and Refund Policy
54
+
55
+ ## Eligibility
56
+ - Items can be returned within 30 days of purchase
57
+ - Items must be unused and in original packaging
58
+ - Receipt or proof of purchase required
59
+
60
+ ## Exceptions
61
+ - Final sale items cannot be returned
62
+ - Personalized items cannot be returned
63
+ - Perishable goods cannot be returned after 7 days
64
+
65
+ ## Refund Process
66
+ - Refunds issued to original payment method
67
+ - Processing takes 5-10 business days
68
+ - Shipping costs are non-refundable unless item was defective
69
+
70
+ ## Exchanges
71
+ - Exchanges available within 30 days
72
+ - Size exchanges free of charge
73
+ - Different item exchanges treated as return + new purchase
74
+
75
+ ## Defective Items
76
+ - Report defects within 14 days
77
+ - Photos required for defect claims
78
+ - Replacement or full refund offered for confirmed defects
79
+ """
80
+
81
+ SUPPORT_GUIDELINES = """# Customer Support Guidelines
82
+
83
+ ## Response Times
84
+ - Chat: Respond within 2 minutes
85
+ - Email: Respond within 4 hours during business hours
86
+ - Phone: Answer within 30 seconds, max hold time 3 minutes
87
+
88
+ ## Escalation Tiers
89
+ - Tier 1: General questions, password resets, basic troubleshooting
90
+ - Tier 2: Technical issues, billing disputes, account problems
91
+ - Tier 3: Complex technical issues, executive escalations
92
+
93
+ ## Refund Authority
94
+ - Tier 1 can issue refunds up to $50
95
+ - Tier 2 can issue refunds up to $200
96
+ - Tier 3 or manager approval needed for refunds over $200
97
+
98
+ ## Documentation
99
+ - Log all customer interactions in CRM
100
+ - Include customer sentiment and issue category
101
+ - Note any promised follow-ups with deadlines
102
+ """
103
+
104
+ SECURITY_POLICY = """# Information Security Policy
105
+
106
+ ## Password Requirements
107
+ - Minimum 12 characters
108
+ - Must include uppercase, lowercase, number, and symbol
109
+ - Change every 90 days
110
+ - Cannot reuse last 10 passwords
111
+
112
+ ## Access Control
113
+ - Principle of least privilege applies
114
+ - Access requests require manager approval
115
+ - Quarterly access reviews mandatory
116
+ - Terminate access within 24 hours of employee departure
117
+
118
+ ## Data Classification
119
+ - Public: Marketing materials, job postings
120
+ - Internal: Company announcements, policies
121
+ - Confidential: Customer data, financials
122
+ - Restricted: PII, payment info, credentials
123
+
124
+ ## Incident Response
125
+ - Report security incidents within 1 hour
126
+ - Do not attempt to investigate independently
127
+ - Preserve evidence (don't delete logs or files)
128
+ - Security team leads all incident response
129
+ """
130
+
131
+ # All policies available as a list
132
+ ALL_POLICIES = [
133
+ ("expense", EXPENSE_POLICY),
134
+ ("hr", HR_HANDBOOK),
135
+ ("refund", REFUND_POLICY),
136
+ ("support", SUPPORT_GUIDELINES),
137
+ ("security", SECURITY_POLICY),
138
+ ]
139
+
140
+ __all__ = [
141
+ "EXPENSE_POLICY",
142
+ "HR_HANDBOOK",
143
+ "REFUND_POLICY",
144
+ "SUPPORT_GUIDELINES",
145
+ "SECURITY_POLICY",
146
+ "ALL_POLICIES",
147
+ ]
148
+