Skip to main content

vynco/
types.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5// ---------------------------------------------------------------------------
6// Pagination
7// ---------------------------------------------------------------------------
8
9/// Generic paginated response wrapper.
10#[derive(Debug, Clone, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct PagedResponse<T> {
13    #[serde(default)]
14    pub items: Vec<T>,
15    #[serde(default)]
16    pub total: i64,
17    #[serde(default)]
18    pub page: i64,
19    #[serde(default)]
20    pub page_size: i64,
21}
22
23// ---------------------------------------------------------------------------
24// Health
25// ---------------------------------------------------------------------------
26
27/// API health status response.
28#[derive(Debug, Clone, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct HealthResponse {
31    #[serde(default)]
32    pub status: String,
33    #[serde(default)]
34    pub database: String,
35    #[serde(default)]
36    pub redis: String,
37    #[serde(default)]
38    pub version: String,
39}
40
41// ---------------------------------------------------------------------------
42// Companies
43// ---------------------------------------------------------------------------
44
45/// A Swiss company record.
46#[derive(Debug, Clone, Default, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct Company {
49    pub uid: String,
50    pub name: String,
51    #[serde(default)]
52    pub canton: Option<String>,
53    #[serde(default)]
54    pub status: Option<String>,
55    #[serde(default)]
56    pub legal_form: Option<String>,
57    #[serde(default)]
58    pub share_capital: Option<f64>,
59    #[serde(default)]
60    pub currency: Option<String>,
61    #[serde(default)]
62    pub purpose: Option<String>,
63    #[serde(default)]
64    pub founding_date: Option<String>,
65    #[serde(default)]
66    pub registration_date: Option<String>,
67    #[serde(default)]
68    pub deletion_date: Option<String>,
69    #[serde(default)]
70    pub legal_seat: Option<String>,
71    #[serde(default)]
72    pub municipality: Option<String>,
73    #[serde(default)]
74    pub data_source: Option<String>,
75    #[serde(default)]
76    pub enrichment_level: Option<String>,
77    #[serde(default)]
78    pub address_street: Option<String>,
79    #[serde(default)]
80    pub address_house_number: Option<String>,
81    #[serde(default)]
82    pub address_zip_code: Option<String>,
83    #[serde(default)]
84    pub address_city: Option<String>,
85    #[serde(default)]
86    pub address_canton: Option<String>,
87    #[serde(default)]
88    pub website: Option<String>,
89    #[serde(default)]
90    pub industry: Option<String>,
91    #[serde(default)]
92    pub sub_industry: Option<String>,
93    #[serde(default)]
94    pub employee_count: Option<i32>,
95    #[serde(default)]
96    pub auditor_name: Option<String>,
97    #[serde(default)]
98    pub auditor_category: Option<String>,
99    #[serde(default)]
100    pub latitude: Option<f64>,
101    #[serde(default)]
102    pub longitude: Option<f64>,
103    #[serde(default)]
104    pub geo_precision: Option<String>,
105    #[serde(default)]
106    pub noga_code: Option<String>,
107    #[serde(default)]
108    pub sanctions_hit: Option<bool>,
109    #[serde(default)]
110    pub last_screened_at: Option<String>,
111    #[serde(default)]
112    pub is_finma_regulated: Option<bool>,
113    #[serde(default)]
114    pub ehraid: Option<i64>,
115    #[serde(default)]
116    pub chid: Option<String>,
117    #[serde(default)]
118    pub cantonal_excerpt_url: Option<String>,
119    #[serde(default)]
120    pub old_names: Option<Vec<String>>,
121    #[serde(default)]
122    pub translations: Option<Vec<String>>,
123    #[serde(default)]
124    pub updated_at: Option<String>,
125    // Enrichment provenance (v3.1+)
126    #[serde(default)]
127    pub direct_parent_lei: Option<String>,
128    #[serde(default)]
129    pub ultimate_parent_lei: Option<String>,
130    #[serde(default)]
131    pub ultimate_parent_name: Option<String>,
132    #[serde(default)]
133    pub gleif_parent_enriched_at: Option<String>,
134    #[serde(default)]
135    pub industry_source: Option<String>,
136    #[serde(default)]
137    pub industry_confidence: Option<f32>,
138    #[serde(default)]
139    pub industry_classified_at: Option<String>,
140    // External identifiers
141    #[serde(default)]
142    pub lei: Option<String>,
143    #[serde(default)]
144    pub duns: Option<String>,
145    #[serde(default)]
146    pub isin: Option<String>,
147}
148
149/// Query parameters for listing companies.
150#[derive(Debug, Clone, Default, Serialize)]
151pub struct CompanyListParams {
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub search: Option<String>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub canton: Option<String>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub changed_since: Option<String>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub status: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub legal_form: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub capital_min: Option<f64>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub capital_max: Option<f64>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub auditor_category: Option<String>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub sort_by: Option<String>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub sort_desc: Option<bool>,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub page: Option<i64>,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub page_size: Option<i64>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub lei: Option<String>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub duns: Option<String>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub isin: Option<String>,
182}
183
184/// Company count response.
185#[derive(Debug, Clone, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct CompanyCount {
188    #[serde(default)]
189    pub count: i64,
190}
191
192// ---------------------------------------------------------------------------
193// Events
194// ---------------------------------------------------------------------------
195
196/// Response wrapper for event listing.
197#[derive(Debug, Clone, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct EventListResponse {
200    #[serde(default)]
201    pub events: Vec<CompanyEvent>,
202    #[serde(default)]
203    pub count: i64,
204}
205
206/// A CloudEvent-style company event.
207#[derive(Debug, Clone, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct CompanyEvent {
210    pub id: String,
211    #[serde(default)]
212    pub ce_type: String,
213    #[serde(default)]
214    pub ce_source: String,
215    #[serde(default)]
216    pub ce_time: String,
217    #[serde(default)]
218    pub company_uid: String,
219    #[serde(default)]
220    pub company_name: String,
221    #[serde(default)]
222    pub category: String,
223    #[serde(default)]
224    pub severity: String,
225    #[serde(default)]
226    pub summary: String,
227    #[serde(default)]
228    pub detail_json: serde_json::Value,
229    #[serde(default)]
230    pub created_at: String,
231}
232
233// ---------------------------------------------------------------------------
234// Auditors
235// ---------------------------------------------------------------------------
236
237/// Auditor history for a company.
238#[derive(Debug, Clone, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct AuditorHistoryResponse {
241    pub company_uid: String,
242    pub company_name: String,
243    #[serde(default)]
244    pub current_auditor: Option<AuditorTenure>,
245    #[serde(default)]
246    pub history: Vec<AuditorTenure>,
247}
248
249/// A single auditor tenure record.
250#[derive(Debug, Clone, Default, Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct AuditorTenure {
253    pub id: String,
254    #[serde(default)]
255    pub company_uid: String,
256    #[serde(default)]
257    pub company_name: String,
258    #[serde(default)]
259    pub auditor_name: String,
260    #[serde(default)]
261    pub appointed_at: Option<String>,
262    #[serde(default)]
263    pub resigned_at: Option<String>,
264    #[serde(default)]
265    pub tenure_years: Option<f64>,
266    #[serde(default)]
267    pub is_current: bool,
268    #[serde(default)]
269    pub source: String,
270}
271
272/// Query parameters for auditor tenure listing.
273#[derive(Debug, Clone, Default, Serialize)]
274pub struct AuditorTenureParams {
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub min_years: Option<f64>,
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub canton: Option<String>,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub page: Option<i64>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub page_size: Option<i64>,
283}
284
285// ---------------------------------------------------------------------------
286// Dashboard
287// ---------------------------------------------------------------------------
288
289/// Admin dashboard response.
290#[derive(Debug, Clone, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct DashboardResponse {
293    #[serde(default)]
294    pub generated_at: String,
295    #[serde(default)]
296    pub data: DataCompleteness,
297    #[serde(default)]
298    pub pipelines: Vec<PipelineStatus>,
299    #[serde(default)]
300    pub auditor_tenures: AuditorTenureStats,
301}
302
303/// Data completeness metrics.
304#[derive(Debug, Clone, Default, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct DataCompleteness {
307    #[serde(default)]
308    pub total_companies: i64,
309    #[serde(default)]
310    pub enriched_companies: i64,
311    #[serde(default)]
312    pub companies_with_industry: i64,
313    #[serde(default)]
314    pub companies_with_geo: i64,
315    #[serde(default)]
316    pub total_persons: i64,
317    #[serde(default)]
318    pub total_changes: i64,
319    #[serde(default)]
320    pub total_sogc_publications: i64,
321}
322
323/// Pipeline run status.
324#[derive(Debug, Clone, Deserialize)]
325#[serde(rename_all = "camelCase")]
326pub struct PipelineStatus {
327    pub id: String,
328    #[serde(default)]
329    pub status: String,
330    #[serde(default)]
331    pub items_processed: i32,
332    #[serde(default)]
333    pub last_completed_at: Option<String>,
334}
335
336/// Auditor tenure aggregate statistics.
337#[derive(Debug, Clone, Default, Deserialize)]
338#[serde(rename_all = "camelCase")]
339pub struct AuditorTenureStats {
340    #[serde(default)]
341    pub total_tracked: i64,
342    #[serde(default)]
343    pub current_auditors: i64,
344    #[serde(default)]
345    pub tenures_over_10_years: i64,
346    #[serde(default)]
347    pub tenures_over_7_years: i64,
348    #[serde(default)]
349    pub avg_tenure_years: f64,
350    #[serde(default)]
351    pub longest_tenure: Option<LongestTenure>,
352}
353
354/// The longest current auditor tenure.
355#[derive(Debug, Clone, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct LongestTenure {
358    #[serde(default)]
359    pub company_uid: String,
360    #[serde(default)]
361    pub company_name: String,
362    #[serde(default)]
363    pub auditor_name: String,
364    #[serde(default)]
365    pub tenure_years: f64,
366}
367
368// ---------------------------------------------------------------------------
369// Screening
370// ---------------------------------------------------------------------------
371
372/// Request body for compliance screening.
373#[derive(Debug, Clone, Serialize)]
374pub struct ScreeningRequest {
375    pub name: String,
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub uid: Option<String>,
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub sources: Option<Vec<String>>,
380}
381
382/// Screening result response.
383#[derive(Debug, Clone, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct ScreeningResponse {
386    #[serde(default)]
387    pub query_name: String,
388    #[serde(default)]
389    pub query_uid: Option<String>,
390    #[serde(default)]
391    pub screened_at: String,
392    #[serde(default)]
393    pub hit_count: i32,
394    #[serde(default)]
395    pub risk_level: String,
396    #[serde(default)]
397    pub hits: Vec<ScreeningHit>,
398    #[serde(default)]
399    pub sources_checked: Vec<String>,
400}
401
402/// A single screening hit.
403#[derive(Debug, Clone, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct ScreeningHit {
406    #[serde(default)]
407    pub source: String,
408    #[serde(default)]
409    pub matched_name: String,
410    #[serde(default)]
411    pub entity_type: String,
412    #[serde(default)]
413    pub score: f64,
414    #[serde(default)]
415    pub datasets: Vec<String>,
416    #[serde(default)]
417    pub details: serde_json::Value,
418}
419
420// ---------------------------------------------------------------------------
421// Watchlists
422// ---------------------------------------------------------------------------
423
424/// A watchlist.
425#[derive(Debug, Clone, Deserialize)]
426#[serde(rename_all = "camelCase")]
427pub struct Watchlist {
428    pub id: String,
429    pub name: String,
430    #[serde(default)]
431    pub description: String,
432    #[serde(default)]
433    pub created_at: String,
434    #[serde(default)]
435    pub updated_at: String,
436}
437
438/// Watchlist summary (used in list responses).
439#[derive(Debug, Clone, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct WatchlistSummary {
442    pub id: String,
443    pub name: String,
444    #[serde(default)]
445    pub description: String,
446    #[serde(default)]
447    pub company_count: i64,
448    #[serde(default)]
449    pub created_at: String,
450}
451
452/// Request body for creating a watchlist.
453#[derive(Debug, Clone, Serialize)]
454pub struct CreateWatchlistRequest {
455    pub name: String,
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub description: Option<String>,
458}
459
460/// Response containing UIDs of companies in a watchlist.
461#[derive(Debug, Clone, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct WatchlistCompaniesResponse {
464    #[serde(default)]
465    pub uids: Vec<String>,
466    /// Enriched company entries with name/status/canton (v3.1+).
467    #[serde(default)]
468    pub companies: Vec<WatchlistCompanyEntry>,
469}
470
471/// Request body for adding companies to a watchlist.
472#[derive(Debug, Clone, Serialize)]
473pub struct AddCompaniesRequest {
474    pub uids: Vec<String>,
475}
476
477/// Response from adding companies to a watchlist.
478#[derive(Debug, Clone, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct AddCompaniesResponse {
481    #[serde(default)]
482    pub added: i64,
483}
484
485// ---------------------------------------------------------------------------
486// Webhooks
487// ---------------------------------------------------------------------------
488
489/// A webhook subscription.
490#[derive(Debug, Clone, Default, Deserialize)]
491#[serde(rename_all = "camelCase")]
492pub struct WebhookSubscription {
493    pub id: String,
494    #[serde(default)]
495    pub url: String,
496    #[serde(default)]
497    pub description: String,
498    #[serde(default)]
499    pub event_filters: Vec<String>,
500    #[serde(default)]
501    pub company_filters: Vec<String>,
502    #[serde(default)]
503    pub status: String,
504    #[serde(default)]
505    pub created_at: String,
506    #[serde(default)]
507    pub updated_at: String,
508}
509
510/// Request body for creating a webhook subscription.
511#[derive(Debug, Clone, Serialize)]
512pub struct CreateWebhookRequest {
513    pub url: String,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub description: Option<String>,
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub event_filters: Option<Vec<String>>,
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub company_filters: Option<Vec<String>>,
520}
521
522/// Response from creating a webhook (includes signing secret).
523#[derive(Debug, Clone, Deserialize)]
524#[serde(rename_all = "camelCase")]
525pub struct CreateWebhookResponse {
526    #[serde(default)]
527    pub webhook: WebhookSubscription,
528    #[serde(default)]
529    pub signing_secret: String,
530}
531
532/// Request body for updating a webhook subscription.
533#[derive(Debug, Clone, Default, Serialize)]
534pub struct UpdateWebhookRequest {
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub url: Option<String>,
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub description: Option<String>,
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub event_filters: Option<Vec<String>>,
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub company_filters: Option<Vec<String>>,
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub status: Option<String>,
545}
546
547/// Response from testing a webhook delivery.
548#[derive(Debug, Clone, Deserialize)]
549#[serde(rename_all = "camelCase")]
550pub struct TestDeliveryResponse {
551    #[serde(default)]
552    pub success: bool,
553    #[serde(default)]
554    pub http_status: Option<i32>,
555    #[serde(default)]
556    pub error: Option<String>,
557}
558
559/// A webhook delivery record.
560#[derive(Debug, Clone, Deserialize)]
561#[serde(rename_all = "camelCase")]
562pub struct WebhookDelivery {
563    pub id: String,
564    #[serde(default)]
565    pub event_id: String,
566    #[serde(default)]
567    pub status: String,
568    #[serde(default)]
569    pub attempt: i32,
570    #[serde(default)]
571    pub http_status: Option<i32>,
572    #[serde(default)]
573    pub error_message: Option<String>,
574    #[serde(default)]
575    pub delivered_at: Option<String>,
576    #[serde(default)]
577    pub created_at: String,
578}
579
580// ---------------------------------------------------------------------------
581// Exports
582// ---------------------------------------------------------------------------
583
584/// Request body for creating a data export.
585#[derive(Debug, Clone, Default, Serialize)]
586pub struct CreateExportRequest {
587    #[serde(skip_serializing_if = "Option::is_none")]
588    pub format: Option<String>,
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub canton: Option<String>,
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub status: Option<String>,
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub changed_since: Option<String>,
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub industry: Option<String>,
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub max_rows: Option<i64>,
599}
600
601/// An export job record.
602#[derive(Debug, Clone, Default, Deserialize)]
603#[serde(rename_all = "camelCase")]
604pub struct ExportJob {
605    pub id: String,
606    #[serde(default)]
607    pub status: String,
608    #[serde(default)]
609    pub format: String,
610    #[serde(default)]
611    pub total_rows: Option<i32>,
612    #[serde(default)]
613    pub file_size_bytes: Option<i64>,
614    #[serde(default)]
615    pub error_message: Option<String>,
616    #[serde(default)]
617    pub created_at: String,
618    #[serde(default)]
619    pub completed_at: Option<String>,
620    #[serde(default)]
621    pub expires_at: Option<String>,
622}
623
624/// Export download response (job metadata + optional data).
625#[derive(Debug, Clone, Deserialize)]
626#[serde(rename_all = "camelCase")]
627pub struct ExportDownload {
628    #[serde(default)]
629    pub job: ExportJob,
630    #[serde(default)]
631    pub data: Option<String>,
632}
633
634// ---------------------------------------------------------------------------
635// AI
636// ---------------------------------------------------------------------------
637
638/// Request body for generating an AI dossier.
639#[derive(Debug, Clone, Serialize)]
640pub struct DossierRequest {
641    pub uid: String,
642    #[serde(skip_serializing_if = "Option::is_none")]
643    pub depth: Option<String>,
644}
645
646/// AI dossier response.
647#[derive(Debug, Clone, Deserialize)]
648#[serde(rename_all = "camelCase")]
649pub struct DossierResponse {
650    #[serde(default)]
651    pub uid: String,
652    #[serde(default)]
653    pub company_name: String,
654    #[serde(default)]
655    pub dossier: String,
656    #[serde(default)]
657    pub sources: Vec<String>,
658    #[serde(default)]
659    pub generated_at: String,
660}
661
662/// Request body for AI-powered search.
663#[derive(Debug, Clone, Serialize)]
664pub struct AiSearchRequest {
665    pub query: String,
666}
667
668/// AI search response.
669#[derive(Debug, Clone, Deserialize)]
670#[serde(rename_all = "camelCase")]
671pub struct AiSearchResponse {
672    #[serde(default)]
673    pub query: String,
674    #[serde(default)]
675    pub explanation: String,
676    #[serde(default)]
677    pub filters_applied: serde_json::Value,
678    #[serde(default)]
679    pub results: Vec<serde_json::Value>,
680    #[serde(default)]
681    pub total: i64,
682}
683
684/// Request body for risk score assessment.
685#[derive(Debug, Clone, Serialize)]
686pub struct RiskScoreRequest {
687    pub uid: String,
688}
689
690/// Risk score response.
691#[derive(Debug, Clone, Deserialize)]
692#[serde(rename_all = "camelCase")]
693pub struct RiskScoreResponse {
694    #[serde(default)]
695    pub uid: String,
696    #[serde(default)]
697    pub company_name: String,
698    #[serde(default)]
699    pub overall_score: i32,
700    #[serde(default)]
701    pub risk_level: String,
702    #[serde(default)]
703    pub breakdown: Vec<RiskFactor>,
704    #[serde(default)]
705    pub assessed_at: String,
706}
707
708/// A single risk factor in a risk score breakdown.
709#[derive(Debug, Clone, Deserialize)]
710#[serde(rename_all = "camelCase")]
711pub struct RiskFactor {
712    #[serde(default)]
713    pub factor: String,
714    #[serde(default)]
715    pub score: i32,
716    #[serde(default)]
717    pub weight: f64,
718    #[serde(default)]
719    pub description: String,
720}
721
722// ---------------------------------------------------------------------------
723// API Keys
724// ---------------------------------------------------------------------------
725
726#[derive(Debug, Clone, Serialize)]
727pub struct CreateApiKeyRequest {
728    #[serde(skip_serializing_if = "Option::is_none")]
729    pub name: Option<String>,
730    #[serde(skip_serializing_if = "Option::is_none")]
731    pub environment: Option<String>,
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub scopes: Option<Vec<String>>,
734}
735
736#[derive(Debug, Clone, Deserialize)]
737#[serde(rename_all = "camelCase")]
738pub struct ApiKeyCreated {
739    pub key: String,
740    pub id: String,
741    #[serde(default)]
742    pub name: String,
743    #[serde(default)]
744    pub prefix: String,
745    #[serde(default)]
746    pub environment: String,
747    #[serde(default)]
748    pub scopes: Vec<String>,
749    pub expires_at: Option<String>,
750    #[serde(default)]
751    pub created_at: String,
752    #[serde(default)]
753    pub warning: String,
754}
755
756#[derive(Debug, Clone, Deserialize)]
757#[serde(rename_all = "camelCase")]
758pub struct ApiKey {
759    pub id: String,
760    #[serde(default)]
761    pub name: String,
762    #[serde(default)]
763    pub prefix: String,
764    #[serde(default)]
765    pub environment: String,
766    #[serde(default)]
767    pub scopes: Vec<String>,
768    #[serde(default)]
769    pub status: String,
770    pub expires_at: Option<String>,
771    #[serde(default)]
772    pub created_at: String,
773    pub last_used_at: Option<String>,
774}
775
776// ---------------------------------------------------------------------------
777// Credits
778// ---------------------------------------------------------------------------
779
780#[derive(Debug, Clone, Deserialize)]
781#[serde(rename_all = "camelCase")]
782pub struct CreditBalance {
783    #[serde(default)]
784    pub balance: i32,
785    #[serde(default)]
786    pub monthly_credits: i64,
787    #[serde(default)]
788    pub used_this_month: i64,
789    #[serde(default)]
790    pub tier: String,
791    #[serde(default)]
792    pub overage_rate: f64,
793}
794
795#[derive(Debug, Clone, Deserialize)]
796#[serde(rename_all = "camelCase")]
797pub struct CreditUsage {
798    #[serde(default)]
799    pub operations: Vec<UsageRow>,
800    #[serde(default)]
801    pub total: i64,
802    #[serde(default)]
803    pub period: UsagePeriod,
804}
805
806#[derive(Debug, Clone, Default, Deserialize)]
807#[serde(rename_all = "camelCase")]
808pub struct UsagePeriod {
809    #[serde(default)]
810    pub since: String,
811    #[serde(default)]
812    pub until: String,
813}
814
815#[derive(Debug, Clone, Deserialize)]
816#[serde(rename_all = "camelCase")]
817pub struct UsageRow {
818    #[serde(default)]
819    pub operation: String,
820    #[serde(default)]
821    pub count: i64,
822    #[serde(default)]
823    pub total_credits: i64,
824}
825
826#[derive(Debug, Clone, Deserialize)]
827#[serde(rename_all = "camelCase")]
828pub struct CreditHistory {
829    #[serde(default)]
830    pub items: Vec<CreditLedgerEntry>,
831    #[serde(default)]
832    pub total: i64,
833}
834
835#[derive(Debug, Clone, Deserialize)]
836#[serde(rename_all = "camelCase")]
837pub struct CreditLedgerEntry {
838    pub id: i64,
839    #[serde(default)]
840    pub entry_type: String,
841    #[serde(default)]
842    pub amount: i32,
843    #[serde(default)]
844    pub balance: i32,
845    #[serde(default)]
846    pub description: String,
847    #[serde(default)]
848    pub created_at: String,
849}
850
851// ---------------------------------------------------------------------------
852// Teams
853// ---------------------------------------------------------------------------
854
855#[derive(Debug, Clone, Deserialize)]
856#[serde(rename_all = "camelCase")]
857pub struct Team {
858    pub id: String,
859    #[serde(default)]
860    pub name: String,
861    #[serde(default)]
862    pub slug: String,
863    #[serde(default)]
864    pub tier: String,
865    #[serde(default)]
866    pub credit_balance: i32,
867    #[serde(default)]
868    pub monthly_credits: i64,
869}
870
871#[derive(Debug, Clone, Serialize)]
872pub struct CreateTeamRequest {
873    #[serde(skip_serializing_if = "Option::is_none")]
874    pub name: Option<String>,
875}
876
877#[derive(Debug, Clone, Deserialize)]
878#[serde(rename_all = "camelCase")]
879pub struct TeamMember {
880    pub id: String,
881    #[serde(default)]
882    pub name: String,
883    #[serde(default)]
884    pub email: String,
885    #[serde(default)]
886    pub role: String,
887    pub last_login_at: Option<String>,
888}
889
890#[derive(Debug, Clone, Serialize)]
891pub struct InviteMemberRequest {
892    pub email: String,
893    #[serde(skip_serializing_if = "Option::is_none")]
894    pub role: Option<String>,
895}
896
897#[derive(Debug, Clone, Deserialize)]
898#[serde(rename_all = "camelCase")]
899pub struct Invitation {
900    pub id: String,
901    #[serde(default)]
902    pub team_id: String,
903    #[serde(default)]
904    pub email: String,
905    #[serde(default)]
906    pub role: String,
907    #[serde(default)]
908    pub token: String,
909    #[serde(default)]
910    pub status: String,
911    #[serde(default)]
912    pub created_at: String,
913    #[serde(default)]
914    pub expires_at: String,
915}
916
917#[derive(Debug, Clone, Serialize)]
918pub struct UpdateMemberRoleRequest {
919    pub role: String,
920}
921
922/// Request body for joining a team via invitation token.
923#[derive(Debug, Clone, Serialize)]
924pub struct JoinTeamRequest {
925    pub token: String,
926}
927
928/// Response from joining a team.
929#[derive(Debug, Clone, Deserialize)]
930#[serde(rename_all = "camelCase")]
931pub struct JoinTeamResponse {
932    #[serde(default)]
933    pub team_id: String,
934    #[serde(default)]
935    pub team_name: String,
936    #[serde(default)]
937    pub role: String,
938}
939
940#[derive(Debug, Clone, Deserialize)]
941#[serde(rename_all = "camelCase")]
942pub struct BillingSummary {
943    #[serde(default)]
944    pub tier: String,
945    #[serde(default)]
946    pub credit_balance: i32,
947    #[serde(default)]
948    pub monthly_credits: i64,
949    #[serde(default)]
950    pub used_this_month: i64,
951    #[serde(default)]
952    pub members: Vec<MemberUsage>,
953}
954
955#[derive(Debug, Clone, Deserialize)]
956#[serde(rename_all = "camelCase")]
957pub struct MemberUsage {
958    pub user_id: String,
959    #[serde(default)]
960    pub name: String,
961    #[serde(default)]
962    pub credits_used: i64,
963}
964
965// ---------------------------------------------------------------------------
966// Billing
967// ---------------------------------------------------------------------------
968
969#[derive(Debug, Clone, Serialize)]
970pub struct CheckoutRequest {
971    pub tier: String,
972}
973
974#[derive(Debug, Clone, Deserialize)]
975#[serde(rename_all = "camelCase")]
976pub struct SessionUrl {
977    pub url: String,
978}
979
980// ---------------------------------------------------------------------------
981// Changes
982// ---------------------------------------------------------------------------
983
984#[derive(Debug, Clone, Default, Serialize)]
985pub struct ChangeListParams {
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub change_type: Option<String>,
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub since: Option<String>,
990    #[serde(skip_serializing_if = "Option::is_none")]
991    pub until: Option<String>,
992    #[serde(skip_serializing_if = "Option::is_none")]
993    pub company_search: Option<String>,
994    #[serde(skip_serializing_if = "Option::is_none")]
995    pub page: Option<i64>,
996    #[serde(skip_serializing_if = "Option::is_none")]
997    pub page_size: Option<i64>,
998}
999
1000#[derive(Debug, Clone, Default, Deserialize)]
1001#[serde(rename_all = "camelCase")]
1002pub struct CompanyChange {
1003    pub id: String,
1004    #[serde(default)]
1005    pub company_uid: String,
1006    pub company_name: Option<String>,
1007    #[serde(default)]
1008    pub change_type: String,
1009    pub field_name: Option<String>,
1010    pub old_value: Option<String>,
1011    pub new_value: Option<String>,
1012    pub description: Option<String>,
1013    pub source: Option<String>,
1014    #[serde(default)]
1015    pub detected_at: String,
1016}
1017
1018#[derive(Debug, Clone, Deserialize)]
1019#[serde(rename_all = "camelCase")]
1020pub struct ChangeStatistics {
1021    #[serde(default)]
1022    pub total_changes: i64,
1023    #[serde(default)]
1024    pub changes_this_week: i64,
1025    #[serde(default)]
1026    pub changes_this_month: i64,
1027    #[serde(default)]
1028    pub by_type: serde_json::Value,
1029}
1030
1031// ---------------------------------------------------------------------------
1032// Persons
1033// ---------------------------------------------------------------------------
1034
1035#[derive(Debug, Clone, Deserialize)]
1036#[serde(rename_all = "camelCase")]
1037pub struct BoardMember {
1038    pub id: String,
1039    pub first_name: Option<String>,
1040    pub last_name: Option<String>,
1041    #[serde(default)]
1042    pub role: String,
1043    #[serde(default)]
1044    pub role_category: String,
1045    pub origin: Option<String>,
1046    pub residence: Option<String>,
1047    pub signing_authority: Option<String>,
1048    pub since: Option<String>,
1049    // Enrichment provenance (v3.1+)
1050    pub role_source: Option<String>,
1051    pub role_confidence: Option<f32>,
1052    pub role_inferred_at: Option<String>,
1053}
1054
1055/// Query parameters for searching persons.
1056#[derive(Debug, Clone, Default, Serialize)]
1057pub struct PersonSearchParams {
1058    #[serde(skip_serializing_if = "Option::is_none")]
1059    pub q: Option<String>,
1060    #[serde(skip_serializing_if = "Option::is_none")]
1061    pub page: Option<i64>,
1062    #[serde(skip_serializing_if = "Option::is_none")]
1063    pub page_size: Option<i64>,
1064}
1065
1066/// A person search result.
1067#[derive(Debug, Clone, Default, Deserialize)]
1068#[serde(rename_all = "camelCase")]
1069pub struct PersonSearchResult {
1070    pub id: String,
1071    #[serde(default)]
1072    pub full_name: String,
1073    pub first_name: Option<String>,
1074    pub last_name: Option<String>,
1075    pub place_of_origin: Option<String>,
1076    pub nationality: Option<String>,
1077    pub role_count: Option<i64>,
1078}
1079
1080/// Detailed person record with roles across companies.
1081#[derive(Debug, Clone, Deserialize)]
1082#[serde(rename_all = "camelCase")]
1083pub struct PersonDetail {
1084    pub id: String,
1085    #[serde(default)]
1086    pub full_name: String,
1087    pub first_name: Option<String>,
1088    pub last_name: Option<String>,
1089    pub place_of_origin: Option<String>,
1090    pub residence: Option<String>,
1091    pub nationality: Option<String>,
1092    #[serde(default)]
1093    pub roles: Vec<PersonRoleDetail>,
1094}
1095
1096/// A person's role at a specific company.
1097#[derive(Debug, Clone, Deserialize)]
1098#[serde(rename_all = "camelCase")]
1099pub struct PersonRoleDetail {
1100    #[serde(default)]
1101    pub company_uid: String,
1102    pub company_name: Option<String>,
1103    #[serde(default)]
1104    pub role_function: String,
1105    #[serde(default)]
1106    pub role_category: String,
1107    pub signing_authority: Option<String>,
1108    pub start_date: Option<String>,
1109    pub end_date: Option<String>,
1110    pub change_action: Option<String>,
1111    pub is_current: Option<bool>,
1112    // Enrichment provenance (v3.1+)
1113    pub role_source: Option<String>,
1114    pub role_confidence: Option<f32>,
1115    pub role_inferred_at: Option<String>,
1116}
1117
1118// ---------------------------------------------------------------------------
1119// Analytics
1120// ---------------------------------------------------------------------------
1121
1122#[derive(Debug, Clone, Deserialize)]
1123#[serde(rename_all = "camelCase")]
1124pub struct CompanyStatistics {
1125    #[serde(default)]
1126    pub total: i64,
1127    #[serde(default)]
1128    pub by_status: HashMap<String, i64>,
1129    #[serde(default)]
1130    pub by_canton: HashMap<String, i64>,
1131    #[serde(default)]
1132    pub by_legal_form: HashMap<String, i64>,
1133}
1134
1135#[derive(Debug, Clone, Deserialize)]
1136#[serde(rename_all = "camelCase")]
1137pub struct CantonDistribution {
1138    #[serde(default)]
1139    pub canton: String,
1140    #[serde(default)]
1141    pub count: i64,
1142    #[serde(default)]
1143    pub percentage: f64,
1144}
1145
1146#[derive(Debug, Clone, Deserialize)]
1147#[serde(rename_all = "camelCase")]
1148pub struct AuditorMarketShare {
1149    #[serde(default)]
1150    pub auditor_name: String,
1151    #[serde(default)]
1152    pub company_count: i64,
1153    #[serde(default)]
1154    pub percentage: f64,
1155}
1156
1157#[derive(Debug, Clone, Serialize)]
1158#[serde(rename_all = "camelCase")]
1159pub struct ClusterRequest {
1160    pub algorithm: String,
1161    #[serde(skip_serializing_if = "Option::is_none")]
1162    pub k: Option<u32>,
1163}
1164
1165#[derive(Debug, Clone, Deserialize)]
1166#[serde(rename_all = "camelCase")]
1167pub struct ClusterResponse {
1168    #[serde(default)]
1169    pub clusters: Vec<ClusterResult>,
1170}
1171
1172#[derive(Debug, Clone, Deserialize)]
1173#[serde(rename_all = "camelCase")]
1174pub struct ClusterResult {
1175    #[serde(default)]
1176    pub id: u32,
1177    #[serde(default)]
1178    pub centroid: serde_json::Value,
1179    #[serde(default)]
1180    pub company_count: i64,
1181    #[serde(default)]
1182    pub sample_companies: Vec<String>,
1183}
1184
1185#[derive(Debug, Clone, Serialize)]
1186#[serde(rename_all = "camelCase")]
1187pub struct AnomalyRequest {
1188    pub algorithm: String,
1189    #[serde(skip_serializing_if = "Option::is_none")]
1190    pub threshold: Option<f64>,
1191}
1192
1193#[derive(Debug, Clone, Deserialize)]
1194#[serde(rename_all = "camelCase")]
1195pub struct AnomalyResponse {
1196    #[serde(default)]
1197    pub anomalies: Vec<serde_json::Value>,
1198    #[serde(default)]
1199    pub total_scanned: i64,
1200    #[serde(default)]
1201    pub threshold: f64,
1202}
1203
1204#[derive(Debug, Clone, Deserialize)]
1205#[serde(rename_all = "camelCase")]
1206pub struct RfmSegmentsResponse {
1207    #[serde(default)]
1208    pub segments: Vec<RfmSegment>,
1209}
1210
1211#[derive(Debug, Clone, Deserialize)]
1212#[serde(rename_all = "camelCase")]
1213pub struct RfmSegment {
1214    #[serde(default)]
1215    pub name: String,
1216    #[serde(default)]
1217    pub count: i64,
1218    #[serde(default)]
1219    pub description: String,
1220}
1221
1222#[derive(Debug, Clone, Default, Serialize)]
1223pub struct CohortParams {
1224    #[serde(skip_serializing_if = "Option::is_none")]
1225    pub group_by: Option<String>,
1226    #[serde(skip_serializing_if = "Option::is_none")]
1227    pub metric: Option<String>,
1228}
1229
1230#[derive(Debug, Clone, Deserialize)]
1231#[serde(rename_all = "camelCase")]
1232pub struct CohortResponse {
1233    #[serde(default)]
1234    pub cohorts: Vec<CohortEntry>,
1235    #[serde(default)]
1236    pub group_by: String,
1237    #[serde(default)]
1238    pub metric: String,
1239}
1240
1241#[derive(Debug, Clone, Deserialize)]
1242#[serde(rename_all = "camelCase")]
1243pub struct CohortEntry {
1244    #[serde(default)]
1245    pub group: String,
1246    #[serde(default)]
1247    pub count: i64,
1248    #[serde(default)]
1249    pub metric: String,
1250}
1251
1252#[derive(Debug, Clone, Default, Serialize)]
1253pub struct CandidateParams {
1254    #[serde(skip_serializing_if = "Option::is_none")]
1255    pub sort_by: Option<String>,
1256    #[serde(skip_serializing_if = "Option::is_none")]
1257    pub canton: Option<String>,
1258    #[serde(skip_serializing_if = "Option::is_none")]
1259    pub page: Option<i64>,
1260    #[serde(skip_serializing_if = "Option::is_none")]
1261    pub page_size: Option<i64>,
1262}
1263
1264#[derive(Debug, Clone, Default, Deserialize)]
1265#[serde(rename_all = "camelCase")]
1266pub struct AuditCandidate {
1267    pub uid: String,
1268    pub name: String,
1269    pub canton: Option<String>,
1270    pub legal_form: Option<String>,
1271    pub share_capital: Option<f64>,
1272    pub auditor_name: Option<String>,
1273    pub auditor_category: Option<String>,
1274}
1275
1276// ---------------------------------------------------------------------------
1277// Dossiers (managed)
1278// ---------------------------------------------------------------------------
1279
1280#[derive(Debug, Clone, Serialize)]
1281#[serde(rename_all = "camelCase")]
1282pub struct CreateDossierRequest {
1283    pub uid: String,
1284    #[serde(skip_serializing_if = "Option::is_none")]
1285    pub level: Option<String>,
1286}
1287
1288#[derive(Debug, Clone, Deserialize)]
1289#[serde(rename_all = "camelCase")]
1290pub struct Dossier {
1291    pub id: String,
1292    #[serde(default)]
1293    pub user_id: String,
1294    #[serde(default)]
1295    pub company_uid: String,
1296    #[serde(default)]
1297    pub company_name: String,
1298    #[serde(default)]
1299    pub level: String,
1300    #[serde(default)]
1301    pub content: String,
1302    #[serde(default)]
1303    pub sources: Vec<String>,
1304    #[serde(default)]
1305    pub created_at: String,
1306}
1307
1308#[derive(Debug, Clone, Deserialize)]
1309#[serde(rename_all = "camelCase")]
1310pub struct DossierSummary {
1311    pub id: String,
1312    #[serde(default)]
1313    pub company_uid: String,
1314    #[serde(default)]
1315    pub company_name: String,
1316    #[serde(default)]
1317    pub level: String,
1318    #[serde(default)]
1319    pub created_at: String,
1320}
1321
1322// ---------------------------------------------------------------------------
1323// Graph / Network
1324// ---------------------------------------------------------------------------
1325
1326#[derive(Debug, Clone, Deserialize)]
1327#[serde(rename_all = "camelCase")]
1328pub struct GraphResponse {
1329    #[serde(default)]
1330    pub nodes: Vec<GraphNode>,
1331    #[serde(default)]
1332    pub links: Vec<GraphLink>,
1333}
1334
1335#[derive(Debug, Clone, Deserialize)]
1336#[serde(rename_all = "camelCase")]
1337pub struct GraphNode {
1338    pub id: String,
1339    #[serde(default)]
1340    pub name: String,
1341    #[serde(default)]
1342    pub uid: String,
1343    #[serde(default, rename = "type")]
1344    pub node_type: String,
1345    pub capital: Option<f64>,
1346    pub canton: Option<String>,
1347    pub status: Option<String>,
1348    pub role: Option<String>,
1349    pub person_id: Option<String>,
1350}
1351
1352#[derive(Debug, Clone, Deserialize)]
1353#[serde(rename_all = "camelCase")]
1354pub struct GraphLink {
1355    #[serde(default)]
1356    pub source: String,
1357    #[serde(default)]
1358    pub target: String,
1359    #[serde(default, rename = "type")]
1360    pub link_type: String,
1361    #[serde(default)]
1362    pub label: String,
1363}
1364
1365#[derive(Debug, Clone, Serialize)]
1366#[serde(rename_all = "camelCase")]
1367pub struct NetworkAnalysisRequest {
1368    pub uids: Vec<String>,
1369    pub overlay: String,
1370}
1371
1372#[derive(Debug, Clone, Deserialize)]
1373#[serde(rename_all = "camelCase")]
1374pub struct NetworkAnalysisResponse {
1375    #[serde(default)]
1376    pub nodes: Vec<GraphNode>,
1377    #[serde(default)]
1378    pub links: Vec<GraphLink>,
1379    #[serde(default)]
1380    pub clusters: Vec<NetworkCluster>,
1381}
1382
1383#[derive(Debug, Clone, Deserialize)]
1384#[serde(rename_all = "camelCase")]
1385pub struct NetworkCluster {
1386    #[serde(default)]
1387    pub id: i32,
1388    #[serde(default)]
1389    pub company_uids: Vec<String>,
1390    #[serde(default)]
1391    pub shared_persons: Vec<String>,
1392}
1393
1394// ---------------------------------------------------------------------------
1395// Company Extended (news, reports, relationships, hierarchy, fingerprint, nearby)
1396// ---------------------------------------------------------------------------
1397
1398#[derive(Debug, Clone, Deserialize)]
1399#[serde(rename_all = "camelCase")]
1400pub struct NewsItem {
1401    pub id: String,
1402    #[serde(default)]
1403    pub title: String,
1404    pub summary: Option<String>,
1405    pub source: Option<String>,
1406    #[serde(default)]
1407    pub source_type: String,
1408    #[serde(default)]
1409    pub published_at: String,
1410    #[serde(default, rename = "sourceUrl")]
1411    pub url: Option<String>,
1412}
1413
1414#[derive(Debug, Clone, Deserialize)]
1415#[serde(rename_all = "camelCase")]
1416pub struct CompanyReport {
1417    #[serde(default)]
1418    pub report_type: String,
1419    pub fiscal_year: Option<i32>,
1420    #[serde(default)]
1421    pub description: String,
1422    pub source_url: Option<String>,
1423    #[serde(default)]
1424    pub publication_date: String,
1425}
1426
1427#[derive(Debug, Clone, Deserialize)]
1428#[serde(rename_all = "camelCase")]
1429pub struct Relationship {
1430    #[serde(default)]
1431    pub related_uid: String,
1432    #[serde(default)]
1433    pub related_name: String,
1434    #[serde(default)]
1435    pub relationship_type: String,
1436    #[serde(default)]
1437    pub shared_persons: Vec<String>,
1438}
1439
1440#[derive(Debug, Clone, Deserialize)]
1441#[serde(rename_all = "camelCase")]
1442pub struct HierarchyResponse {
1443    pub parent: Option<HierarchyEntity>,
1444    #[serde(default)]
1445    pub subsidiaries: Vec<HierarchyEntity>,
1446    #[serde(default)]
1447    pub siblings: Vec<HierarchyEntity>,
1448}
1449
1450#[derive(Debug, Clone, Deserialize)]
1451#[serde(rename_all = "camelCase")]
1452pub struct Fingerprint {
1453    #[serde(default)]
1454    pub company_uid: String,
1455    #[serde(default)]
1456    pub name: String,
1457    pub industry_sector: Option<String>,
1458    pub industry_group: Option<String>,
1459    pub industry: Option<String>,
1460    pub size_category: Option<String>,
1461    pub employee_count_estimate: Option<i32>,
1462    pub capital_amount: Option<f64>,
1463    pub capital_currency: Option<String>,
1464    pub revenue: Option<f64>,
1465    pub net_income: Option<f64>,
1466    pub auditor_tier: Option<String>,
1467    #[serde(default)]
1468    pub change_frequency: i64,
1469    #[serde(default)]
1470    pub board_size: i64,
1471    #[serde(default)]
1472    pub company_age: i64,
1473    #[serde(default)]
1474    pub canton: String,
1475    #[serde(default)]
1476    pub legal_form: String,
1477    #[serde(default)]
1478    pub has_parent_company: bool,
1479    #[serde(default)]
1480    pub subsidiary_count: i64,
1481    #[serde(default)]
1482    pub generated_at: String,
1483    #[serde(default)]
1484    pub fingerprint_version: String,
1485    // Swiss register entry date (v3.1+)
1486    pub registration_date: Option<String>,
1487}
1488
1489#[derive(Debug, Clone, Default, Serialize)]
1490pub struct NearbyParams {
1491    pub lat: f64,
1492    pub lng: f64,
1493    #[serde(skip_serializing_if = "Option::is_none")]
1494    pub radius_km: Option<f64>,
1495    #[serde(skip_serializing_if = "Option::is_none")]
1496    pub limit: Option<i64>,
1497}
1498
1499#[derive(Debug, Clone, Deserialize)]
1500#[serde(rename_all = "camelCase")]
1501pub struct NearbyCompany {
1502    pub uid: String,
1503    pub name: String,
1504    #[serde(default)]
1505    pub distance: f64,
1506    #[serde(default)]
1507    pub latitude: f64,
1508    #[serde(default)]
1509    pub longitude: f64,
1510}
1511
1512// ---------------------------------------------------------------------------
1513// Company full (composite endpoint)
1514// ---------------------------------------------------------------------------
1515
1516/// Full company details with persons, changes, and relationships.
1517#[derive(Debug, Clone, Deserialize)]
1518#[serde(rename_all = "camelCase")]
1519pub struct CompanyFullResponse {
1520    #[serde(default)]
1521    pub company: Company,
1522    #[serde(default)]
1523    pub persons: Vec<PersonEntry>,
1524    #[serde(default)]
1525    pub recent_changes: Vec<ChangeEntry>,
1526    #[serde(default)]
1527    pub relationships: Vec<RelationshipEntry>,
1528}
1529
1530/// A person entry in a company's full response.
1531#[derive(Debug, Clone, Deserialize)]
1532#[serde(rename_all = "camelCase")]
1533pub struct PersonEntry {
1534    pub person_id: Option<String>,
1535    pub first_name: Option<String>,
1536    pub last_name: Option<String>,
1537    #[serde(default)]
1538    pub role: String,
1539    pub since: Option<String>,
1540    pub until: Option<String>,
1541    // Enrichment provenance (v3.1+)
1542    pub role_source: Option<String>,
1543    pub role_confidence: Option<f32>,
1544    pub role_inferred_at: Option<String>,
1545}
1546
1547/// A recent change entry in a company's full response.
1548#[derive(Debug, Clone, Deserialize)]
1549#[serde(rename_all = "camelCase")]
1550pub struct ChangeEntry {
1551    pub id: String,
1552    #[serde(default)]
1553    pub company_uid: String,
1554    pub change_type: Option<String>,
1555    pub field_name: Option<String>,
1556    pub old_value: Option<String>,
1557    pub new_value: Option<String>,
1558    #[serde(default)]
1559    pub detected_at: String,
1560    pub source_date: Option<String>,
1561}
1562
1563/// A relationship entry in a company's full response.
1564#[derive(Debug, Clone, Deserialize)]
1565#[serde(rename_all = "camelCase")]
1566pub struct RelationshipEntry {
1567    #[serde(default)]
1568    pub related_uid: String,
1569    pub related_name: Option<String>,
1570    #[serde(default)]
1571    pub relationship_type: String,
1572}
1573
1574// ---------------------------------------------------------------------------
1575// Classification
1576// ---------------------------------------------------------------------------
1577
1578/// Industry classification for a company.
1579#[derive(Debug, Clone, Deserialize)]
1580#[serde(rename_all = "camelCase")]
1581pub struct Classification {
1582    #[serde(default)]
1583    pub company_uid: String,
1584    pub sector_code: Option<String>,
1585    pub sector_name: Option<String>,
1586    pub group_code: Option<String>,
1587    pub group_name: Option<String>,
1588    pub industry_code: Option<String>,
1589    pub industry_name: Option<String>,
1590    pub sub_industry_code: Option<String>,
1591    pub sub_industry_name: Option<String>,
1592    #[serde(default)]
1593    pub method: String,
1594    #[serde(default)]
1595    pub classified_at: String,
1596    pub auditor_category: Option<String>,
1597    #[serde(default)]
1598    pub is_finma_regulated: bool,
1599    // Enrichment provenance (v3.1+)
1600    #[serde(default)]
1601    pub industry_source: Option<String>,
1602    #[serde(default)]
1603    pub industry_confidence: Option<f32>,
1604}
1605
1606// ---------------------------------------------------------------------------
1607// Corporate structure
1608// ---------------------------------------------------------------------------
1609
1610/// Corporate structure showing head offices, branches, and M&A relationships.
1611#[derive(Debug, Clone, Deserialize)]
1612#[serde(rename_all = "camelCase")]
1613pub struct CorporateStructure {
1614    #[serde(default)]
1615    pub head_offices: Vec<RelatedCompanyEntry>,
1616    #[serde(default)]
1617    pub branch_offices: Vec<RelatedCompanyEntry>,
1618    #[serde(default)]
1619    pub acquisitions: Vec<RelatedCompanyEntry>,
1620    #[serde(default)]
1621    pub acquired_by: Vec<RelatedCompanyEntry>,
1622}
1623
1624/// A related company entry in a corporate structure.
1625#[derive(Debug, Clone, Deserialize)]
1626#[serde(rename_all = "camelCase")]
1627pub struct RelatedCompanyEntry {
1628    #[serde(default)]
1629    pub uid: String,
1630    #[serde(default)]
1631    pub name: String,
1632}
1633
1634// ---------------------------------------------------------------------------
1635// Notes
1636// ---------------------------------------------------------------------------
1637
1638/// A user note on a company.
1639#[derive(Debug, Clone, Deserialize)]
1640#[serde(rename_all = "camelCase")]
1641pub struct Note {
1642    pub id: String,
1643    #[serde(default)]
1644    pub company_uid: String,
1645    #[serde(default)]
1646    pub content: String,
1647    #[serde(default)]
1648    pub note_type: String,
1649    #[serde(default)]
1650    pub rating: Option<i32>,
1651    #[serde(default)]
1652    pub is_private: bool,
1653    #[serde(default)]
1654    pub created_at: String,
1655    #[serde(default)]
1656    pub updated_at: String,
1657}
1658
1659/// Request body for creating a note on a company.
1660#[derive(Debug, Clone, Serialize)]
1661#[serde(rename_all = "camelCase")]
1662pub struct CreateNoteRequest {
1663    pub content: String,
1664    #[serde(skip_serializing_if = "Option::is_none")]
1665    pub note_type: Option<String>,
1666    #[serde(skip_serializing_if = "Option::is_none")]
1667    pub rating: Option<i32>,
1668    #[serde(skip_serializing_if = "Option::is_none")]
1669    pub is_private: Option<bool>,
1670}
1671
1672/// Request body for updating a note.
1673#[derive(Debug, Clone, Default, Serialize)]
1674#[serde(rename_all = "camelCase")]
1675pub struct UpdateNoteRequest {
1676    #[serde(skip_serializing_if = "Option::is_none")]
1677    pub content: Option<String>,
1678    #[serde(skip_serializing_if = "Option::is_none")]
1679    pub note_type: Option<String>,
1680    #[serde(skip_serializing_if = "Option::is_none")]
1681    pub rating: Option<i32>,
1682    #[serde(skip_serializing_if = "Option::is_none")]
1683    pub is_private: Option<bool>,
1684}
1685
1686// ---------------------------------------------------------------------------
1687// Tags
1688// ---------------------------------------------------------------------------
1689
1690/// A user tag on a company.
1691#[derive(Debug, Clone, Deserialize)]
1692#[serde(rename_all = "camelCase")]
1693pub struct Tag {
1694    pub id: String,
1695    #[serde(default)]
1696    pub company_uid: String,
1697    #[serde(default)]
1698    pub tag_name: String,
1699    #[serde(default)]
1700    pub color: Option<String>,
1701    #[serde(default)]
1702    pub created_at: String,
1703}
1704
1705/// Request body for creating a tag on a company.
1706#[derive(Debug, Clone, Serialize)]
1707#[serde(rename_all = "camelCase")]
1708pub struct CreateTagRequest {
1709    pub tag_name: String,
1710    #[serde(skip_serializing_if = "Option::is_none")]
1711    pub color: Option<String>,
1712}
1713
1714/// Summary of a user's tag usage across companies.
1715#[derive(Debug, Clone, Deserialize)]
1716#[serde(rename_all = "camelCase")]
1717pub struct TagSummary {
1718    #[serde(default)]
1719    pub tag_name: String,
1720    #[serde(default)]
1721    pub count: i64,
1722}
1723
1724// ---------------------------------------------------------------------------
1725// Excel export
1726// ---------------------------------------------------------------------------
1727
1728/// Request body for Excel/CSV export of companies.
1729#[derive(Debug, Clone, Default, Serialize)]
1730#[serde(rename_all = "camelCase")]
1731pub struct ExcelExportRequest {
1732    #[serde(skip_serializing_if = "Option::is_none")]
1733    pub uids: Option<Vec<String>>,
1734    #[serde(skip_serializing_if = "Option::is_none")]
1735    pub filter: Option<ExcelExportFilter>,
1736    #[serde(skip_serializing_if = "Option::is_none")]
1737    pub fields: Option<Vec<String>>,
1738}
1739
1740/// Filter criteria for Excel export.
1741#[derive(Debug, Clone, Default, Serialize)]
1742#[serde(rename_all = "camelCase")]
1743pub struct ExcelExportFilter {
1744    #[serde(skip_serializing_if = "Option::is_none")]
1745    pub canton: Option<String>,
1746    #[serde(skip_serializing_if = "Option::is_none")]
1747    pub search: Option<String>,
1748    #[serde(skip_serializing_if = "Option::is_none")]
1749    pub status: Option<String>,
1750    #[serde(skip_serializing_if = "Option::is_none")]
1751    pub auditor_category: Option<String>,
1752}
1753
1754// ---------------------------------------------------------------------------
1755// Acquisitions (M&A)
1756// ---------------------------------------------------------------------------
1757
1758/// An M&A relationship record.
1759#[derive(Debug, Clone, Deserialize)]
1760#[serde(rename_all = "camelCase")]
1761pub struct Acquisition {
1762    #[serde(default)]
1763    pub acquirer_uid: String,
1764    #[serde(default)]
1765    pub acquired_uid: String,
1766    pub acquirer_name: Option<String>,
1767    pub acquired_name: Option<String>,
1768    #[serde(default)]
1769    pub created_at: String,
1770}
1771
1772// ---------------------------------------------------------------------------
1773// Compare
1774// ---------------------------------------------------------------------------
1775
1776#[derive(Debug, Clone, Serialize)]
1777#[serde(rename_all = "camelCase")]
1778pub struct CompareRequest {
1779    pub uids: Vec<String>,
1780}
1781
1782#[derive(Debug, Clone, Deserialize)]
1783#[serde(rename_all = "camelCase")]
1784pub struct CompareResponse {
1785    #[serde(default)]
1786    pub uids: Vec<String>,
1787    #[serde(default)]
1788    pub names: Vec<String>,
1789    #[serde(default)]
1790    pub dimensions: Vec<ComparisonDimension>,
1791}
1792
1793#[derive(Debug, Clone, Deserialize)]
1794#[serde(rename_all = "camelCase")]
1795pub struct ComparisonDimension {
1796    #[serde(default)]
1797    pub field: String,
1798    #[serde(default)]
1799    pub label: String,
1800    #[serde(default)]
1801    pub values: Vec<Option<String>>,
1802}
1803
1804// ---------------------------------------------------------------------------
1805// Hierarchy (typed entity — replaces the untyped Any on HierarchyResponse)
1806// ---------------------------------------------------------------------------
1807
1808/// A company entity in a hierarchy response.
1809#[derive(Debug, Clone, Deserialize)]
1810#[serde(rename_all = "camelCase")]
1811pub struct HierarchyEntity {
1812    #[serde(default)]
1813    pub uid: String,
1814    #[serde(default)]
1815    pub name: String,
1816    pub confidence: Option<String>,
1817    pub shared_person_count: Option<i64>,
1818    pub shared_persons: Option<Vec<String>>,
1819}
1820
1821// ---------------------------------------------------------------------------
1822// Timeline (v3.1+)
1823// ---------------------------------------------------------------------------
1824
1825/// A single event on a company timeline.
1826#[derive(Debug, Clone, Deserialize)]
1827#[serde(rename_all = "camelCase")]
1828pub struct TimelineEvent {
1829    pub id: String,
1830    #[serde(default)]
1831    pub category: String,
1832    pub field_name: Option<String>,
1833    pub old_value: Option<String>,
1834    pub new_value: Option<String>,
1835    pub summary: Option<String>,
1836    pub source: Option<String>,
1837    pub severity: Option<String>,
1838    #[serde(default)]
1839    pub date: String,
1840}
1841
1842/// Chronological timeline of a company's changes and events.
1843#[derive(Debug, Clone, Deserialize)]
1844#[serde(rename_all = "camelCase")]
1845pub struct TimelineResponse {
1846    #[serde(default)]
1847    pub uid: String,
1848    #[serde(default)]
1849    pub company_name: String,
1850    #[serde(default)]
1851    pub events: Vec<TimelineEvent>,
1852    #[serde(default)]
1853    pub total_events: i64,
1854}
1855
1856/// Query parameters for timeline endpoints.
1857#[derive(Debug, Clone, Default, Serialize)]
1858pub struct TimelineParams {
1859    #[serde(skip_serializing_if = "Option::is_none")]
1860    pub since: Option<String>,
1861    #[serde(skip_serializing_if = "Option::is_none")]
1862    pub until: Option<String>,
1863    #[serde(skip_serializing_if = "Option::is_none", rename = "changeType")]
1864    pub change_type: Option<String>,
1865}
1866
1867/// AI-generated narrative summary of a company timeline.
1868#[derive(Debug, Clone, Deserialize)]
1869#[serde(rename_all = "camelCase")]
1870pub struct TimelineSummaryResponse {
1871    #[serde(default)]
1872    pub uid: String,
1873    #[serde(default)]
1874    pub company_name: String,
1875    #[serde(default)]
1876    pub summary: String,
1877    #[serde(default)]
1878    pub event_count: i64,
1879    #[serde(default)]
1880    pub generated_at: String,
1881}
1882
1883// ---------------------------------------------------------------------------
1884// Similar companies (v3.1+)
1885// ---------------------------------------------------------------------------
1886
1887/// A company similar to a given query company.
1888#[derive(Debug, Clone, Deserialize)]
1889#[serde(rename_all = "camelCase")]
1890pub struct SimilarCompanyResult {
1891    pub uid: String,
1892    pub name: String,
1893    pub canton: Option<String>,
1894    pub industry: Option<String>,
1895    pub legal_form: Option<String>,
1896    pub share_capital: Option<f64>,
1897    pub status: Option<String>,
1898    #[serde(default)]
1899    pub similarity_score: i32,
1900    #[serde(default)]
1901    pub matching_dimensions: Vec<String>,
1902}
1903
1904/// Response containing companies similar to a query company.
1905#[derive(Debug, Clone, Deserialize)]
1906#[serde(rename_all = "camelCase")]
1907pub struct SimilarCompaniesResponse {
1908    #[serde(default)]
1909    pub company_uid: String,
1910    #[serde(default)]
1911    pub company_name: String,
1912    #[serde(default)]
1913    pub results: Vec<SimilarCompanyResult>,
1914}
1915
1916// ---------------------------------------------------------------------------
1917// UBO / Ownership (v3.1+)
1918//
1919// Non-Swiss parent entities resolved via GLEIF appear with synthetic
1920// identifiers of the form `LEI:<20-char-lei>` in the `*_uid` fields of
1921// `UboPerson`, `ChainLink`, and `OwnershipLink`. These are NOT resolvable
1922// via `companies().get()`.
1923// ---------------------------------------------------------------------------
1924
1925/// A natural person identified as an ultimate beneficial owner.
1926#[derive(Debug, Clone, Deserialize)]
1927#[serde(rename_all = "camelCase")]
1928pub struct UboPerson {
1929    #[serde(default)]
1930    pub person_id: i64,
1931    #[serde(default)]
1932    pub name: String,
1933    #[serde(default)]
1934    pub controlling_entity_uid: String,
1935    #[serde(default)]
1936    pub controlling_entity_name: String,
1937    #[serde(default)]
1938    pub role: String,
1939    pub signing_authority: Option<String>,
1940    #[serde(default)]
1941    pub path_length: i32,
1942}
1943
1944/// A single link in an ownership chain.
1945#[derive(Debug, Clone, Deserialize)]
1946#[serde(rename_all = "camelCase")]
1947pub struct ChainLink {
1948    #[serde(default)]
1949    pub from_uid: String,
1950    #[serde(default)]
1951    pub from_name: String,
1952    #[serde(default)]
1953    pub to_uid: String,
1954    #[serde(default)]
1955    pub to_name: String,
1956    #[serde(default)]
1957    pub depth: i32,
1958}
1959
1960/// Ultimate beneficial owner resolution response.
1961///
1962/// When the backend cannot fully resolve the chain, `data_coverage_note`
1963/// contains a human-readable explanation (e.g. missing GLEIF enrichment).
1964#[derive(Debug, Clone, Deserialize)]
1965#[serde(rename_all = "camelCase")]
1966pub struct UboResponse {
1967    #[serde(default)]
1968    pub uid: String,
1969    #[serde(default)]
1970    pub company_name: String,
1971    #[serde(default)]
1972    pub ubo_persons: Vec<UboPerson>,
1973    #[serde(default)]
1974    pub ownership_chain: Vec<ChainLink>,
1975    #[serde(default)]
1976    pub chain_depth: i32,
1977    #[serde(default)]
1978    pub risk_flags: Vec<String>,
1979    pub data_coverage_note: Option<String>,
1980}
1981
1982/// Request body for ownership.trace.
1983#[derive(Debug, Clone, Default, Serialize)]
1984#[serde(rename_all = "camelCase")]
1985pub struct OwnershipRequest {
1986    #[serde(skip_serializing_if = "Option::is_none")]
1987    pub max_depth: Option<i32>,
1988}
1989
1990/// A company entity in an ownership chain.
1991#[derive(Debug, Clone, Deserialize)]
1992#[serde(rename_all = "camelCase")]
1993pub struct OwnershipEntity {
1994    #[serde(default)]
1995    pub uid: String,
1996    #[serde(default)]
1997    pub name: String,
1998    pub canton: Option<String>,
1999    pub status: Option<String>,
2000    pub legal_form: Option<String>,
2001    pub share_capital: Option<f64>,
2002}
2003
2004/// A single directional relationship in an ownership chain.
2005#[derive(Debug, Clone, Deserialize)]
2006#[serde(rename_all = "camelCase")]
2007pub struct OwnershipLink {
2008    #[serde(default)]
2009    pub source_uid: String,
2010    #[serde(default)]
2011    pub source_name: String,
2012    #[serde(default)]
2013    pub target_uid: String,
2014    #[serde(default)]
2015    pub target_name: String,
2016    #[serde(default)]
2017    pub relationship_type: String,
2018    #[serde(default)]
2019    pub depth: i32,
2020}
2021
2022/// A person's role at a specific company in an ownership chain.
2023#[derive(Debug, Clone, Deserialize)]
2024#[serde(rename_all = "camelCase")]
2025pub struct PersonCompanyRole {
2026    #[serde(default)]
2027    pub company_uid: String,
2028    #[serde(default)]
2029    pub company_name: String,
2030    #[serde(default)]
2031    pub role: String,
2032}
2033
2034/// A person with significant roles across the ownership chain.
2035#[derive(Debug, Clone, Deserialize)]
2036#[serde(rename_all = "camelCase")]
2037pub struct KeyPerson {
2038    #[serde(default)]
2039    pub name: String,
2040    #[serde(default)]
2041    pub companies: Vec<PersonCompanyRole>,
2042}
2043
2044/// A detected circular ownership pattern.
2045#[derive(Debug, Clone, Deserialize)]
2046#[serde(rename_all = "camelCase")]
2047pub struct CircularFlag {
2048    #[serde(default)]
2049    pub loop_uids: Vec<String>,
2050    #[serde(default)]
2051    pub description: String,
2052}
2053
2054/// Full ownership trace response from `POST /ownership/{uid}`.
2055#[derive(Debug, Clone, Deserialize)]
2056#[serde(rename_all = "camelCase")]
2057pub struct OwnershipResponse {
2058    #[serde(default)]
2059    pub uid: String,
2060    #[serde(default)]
2061    pub company_name: String,
2062    #[serde(default)]
2063    pub ownership_chain: Vec<OwnershipLink>,
2064    pub ultimate_parent: Option<OwnershipEntity>,
2065    #[serde(default)]
2066    pub key_persons: Vec<KeyPerson>,
2067    #[serde(default)]
2068    pub circular_flags: Vec<CircularFlag>,
2069    #[serde(default)]
2070    pub risk_level: String,
2071    #[serde(default)]
2072    pub assessed_at: String,
2073}
2074
2075// ---------------------------------------------------------------------------
2076// Media (v3.1+)
2077// ---------------------------------------------------------------------------
2078
2079/// A media/news item with optional sentiment analysis.
2080#[derive(Debug, Clone, Deserialize)]
2081#[serde(rename_all = "camelCase")]
2082pub struct MediaItem {
2083    pub id: String,
2084    #[serde(default)]
2085    pub title: String,
2086    pub summary: Option<String>,
2087    pub source: Option<String>,
2088    pub published_at: Option<String>,
2089    pub url: Option<String>,
2090    pub sentiment_score: Option<f32>,
2091    pub sentiment_label: Option<String>,
2092    pub topics: Option<Vec<String>>,
2093    pub risk_relevance: Option<f32>,
2094}
2095
2096/// Response containing a list of media items.
2097#[derive(Debug, Clone, Deserialize)]
2098#[serde(rename_all = "camelCase")]
2099pub struct MediaResponse {
2100    #[serde(default)]
2101    pub items: Vec<MediaItem>,
2102    #[serde(default)]
2103    pub total: i64,
2104}
2105
2106/// Query parameters for `companies.media()`.
2107#[derive(Debug, Clone, Default, Serialize)]
2108pub struct MediaParams {
2109    #[serde(skip_serializing_if = "Option::is_none")]
2110    pub sentiment: Option<String>,
2111    #[serde(skip_serializing_if = "Option::is_none")]
2112    pub since: Option<String>,
2113    #[serde(skip_serializing_if = "Option::is_none")]
2114    pub limit: Option<i64>,
2115}
2116
2117/// Response from triggering LLM sentiment analysis on media items.
2118#[derive(Debug, Clone, Deserialize)]
2119#[serde(rename_all = "camelCase")]
2120pub struct MediaAnalysisResponse {
2121    #[serde(default)]
2122    pub analyzed_count: i32,
2123    #[serde(default)]
2124    pub message: String,
2125}
2126
2127// ---------------------------------------------------------------------------
2128// Alerts (v3.1+)
2129// ---------------------------------------------------------------------------
2130
2131/// A saved alert that triggers on matching query results.
2132#[derive(Debug, Clone, Deserialize)]
2133#[serde(rename_all = "camelCase")]
2134pub struct Alert {
2135    pub id: String,
2136    pub name: String,
2137    #[serde(default)]
2138    pub query_params: serde_json::Value,
2139    pub webhook_url: Option<String>,
2140    #[serde(default)]
2141    pub frequency: String,
2142    #[serde(default)]
2143    pub is_active: bool,
2144    pub saved_search_id: Option<String>,
2145    pub last_triggered_at: Option<String>,
2146    pub last_result_count: Option<i32>,
2147    #[serde(default)]
2148    pub trigger_count: i32,
2149    #[serde(default)]
2150    pub created_at: String,
2151    #[serde(default)]
2152    pub updated_at: String,
2153}
2154
2155/// Request body for creating an alert.
2156#[derive(Debug, Clone, Serialize)]
2157#[serde(rename_all = "camelCase")]
2158pub struct CreateAlertRequest {
2159    pub name: String,
2160    pub query_params: serde_json::Value,
2161    #[serde(skip_serializing_if = "Option::is_none")]
2162    pub webhook_url: Option<String>,
2163    #[serde(skip_serializing_if = "Option::is_none")]
2164    pub frequency: Option<String>,
2165    #[serde(skip_serializing_if = "Option::is_none")]
2166    pub saved_search_id: Option<String>,
2167}
2168
2169// ---------------------------------------------------------------------------
2170// Analytics — flows, migrations, benchmark (v3.1+)
2171// ---------------------------------------------------------------------------
2172
2173/// A single period of company registration/dissolution flow.
2174#[derive(Debug, Clone, Deserialize)]
2175#[serde(rename_all = "camelCase")]
2176pub struct FlowDataPoint {
2177    #[serde(default)]
2178    pub period: String,
2179    #[serde(default)]
2180    pub group: String,
2181    #[serde(default)]
2182    pub registrations: i64,
2183    #[serde(default)]
2184    pub dissolutions: i64,
2185    #[serde(default)]
2186    pub net: i64,
2187}
2188
2189/// Market flow analytics response.
2190///
2191/// `data_coverage_note` surfaces known asymmetries (e.g. dissolution detection
2192/// started later than registration detection).
2193#[derive(Debug, Clone, Deserialize)]
2194#[serde(rename_all = "camelCase")]
2195pub struct FlowsResponse {
2196    #[serde(default)]
2197    pub flows: Vec<FlowDataPoint>,
2198    pub data_coverage_note: Option<String>,
2199}
2200
2201/// Query parameters for `analytics.flows()`.
2202#[derive(Debug, Clone, Default, Serialize)]
2203pub struct FlowsParams {
2204    #[serde(skip_serializing_if = "Option::is_none")]
2205    pub period: Option<String>,
2206    #[serde(skip_serializing_if = "Option::is_none")]
2207    pub since: Option<String>,
2208    #[serde(skip_serializing_if = "Option::is_none", rename = "groupBy")]
2209    pub group_by: Option<String>,
2210}
2211
2212/// A single canton-to-canton migration flow.
2213#[derive(Debug, Clone, Deserialize)]
2214#[serde(rename_all = "camelCase")]
2215pub struct MigrationFlow {
2216    #[serde(default)]
2217    pub from_canton: String,
2218    #[serde(default)]
2219    pub to_canton: String,
2220    #[serde(default)]
2221    pub count: i64,
2222}
2223
2224/// Canton migration analytics response.
2225#[derive(Debug, Clone, Deserialize)]
2226#[serde(rename_all = "camelCase")]
2227pub struct MigrationResponse {
2228    #[serde(default)]
2229    pub flows: Vec<MigrationFlow>,
2230    #[serde(default)]
2231    pub top_flows: Vec<MigrationFlow>,
2232}
2233
2234/// A single benchmarking dimension.
2235#[derive(Debug, Clone, Deserialize)]
2236#[serde(rename_all = "camelCase")]
2237pub struct BenchmarkDimension {
2238    #[serde(default)]
2239    pub name: String,
2240    pub company_value: Option<f64>,
2241    pub industry_median: Option<f64>,
2242    pub percentile: Option<f64>,
2243    pub peers_with_data: Option<i64>,
2244}
2245
2246/// Industry benchmarking response — how a company compares to peers.
2247#[derive(Debug, Clone, Deserialize)]
2248#[serde(rename_all = "camelCase")]
2249pub struct BenchmarkResponse {
2250    #[serde(default)]
2251    pub uid: String,
2252    #[serde(default)]
2253    pub company_name: String,
2254    pub industry: Option<String>,
2255    #[serde(default)]
2256    pub peer_count: i64,
2257    #[serde(default)]
2258    pub dimensions: Vec<BenchmarkDimension>,
2259}
2260
2261/// Query parameters for `analytics.benchmark()`.
2262#[derive(Debug, Clone, Serialize)]
2263pub struct BenchmarkParams {
2264    pub uid: String,
2265    #[serde(skip_serializing_if = "Option::is_none")]
2266    pub dimensions: Option<String>,
2267}
2268
2269// ---------------------------------------------------------------------------
2270// Batch screening & batch risk scoring (v3.1+)
2271// ---------------------------------------------------------------------------
2272
2273/// Batch screening request body (up to 100 UIDs).
2274#[derive(Debug, Clone, Serialize)]
2275pub struct BatchScreeningRequest {
2276    pub uids: Vec<String>,
2277}
2278
2279/// A summary of a single screening hit within a batch result.
2280#[derive(Debug, Clone, Deserialize)]
2281#[serde(rename_all = "camelCase")]
2282pub struct BatchScreeningHitSummary {
2283    #[serde(default)]
2284    pub source: String,
2285    #[serde(default)]
2286    pub matched_name: String,
2287    #[serde(default)]
2288    pub score: f64,
2289}
2290
2291/// Screening result for a single company in a batch request.
2292#[derive(Debug, Clone, Deserialize)]
2293#[serde(rename_all = "camelCase")]
2294pub struct BatchScreeningResultByUid {
2295    #[serde(default)]
2296    pub uid: String,
2297    #[serde(default)]
2298    pub company_name: String,
2299    #[serde(default)]
2300    pub risk_level: String,
2301    #[serde(default)]
2302    pub total_hits: i32,
2303    #[serde(default)]
2304    pub sources_checked: Vec<String>,
2305    #[serde(default)]
2306    pub hits: Vec<BatchScreeningHitSummary>,
2307}
2308
2309/// Response from a batch screening request.
2310#[derive(Debug, Clone, Deserialize)]
2311#[serde(rename_all = "camelCase")]
2312pub struct BatchScreeningResponse {
2313    #[serde(default)]
2314    pub results: Vec<BatchScreeningResultByUid>,
2315}
2316
2317/// Batch risk-score request body (up to 50 UIDs).
2318#[derive(Debug, Clone, Serialize)]
2319pub struct BatchRiskScoreRequest {
2320    pub uids: Vec<String>,
2321}
2322
2323/// A summary risk score for a single company in a batch request.
2324#[derive(Debug, Clone, Deserialize)]
2325#[serde(rename_all = "camelCase")]
2326pub struct RiskScoreResult {
2327    #[serde(default)]
2328    pub uid: String,
2329    #[serde(default)]
2330    pub company_name: String,
2331    #[serde(default)]
2332    pub overall_score: i32,
2333    #[serde(default)]
2334    pub risk_level: String,
2335}
2336
2337/// Response from a batch risk scoring request.
2338#[derive(Debug, Clone, Deserialize)]
2339#[serde(rename_all = "camelCase")]
2340pub struct BatchRiskScoreResponse {
2341    #[serde(default)]
2342    pub results: Vec<RiskScoreResult>,
2343}
2344
2345// ---------------------------------------------------------------------------
2346// Person network (v3.1+)
2347// ---------------------------------------------------------------------------
2348
2349/// Summary of a person in a network response.
2350#[derive(Debug, Clone, Deserialize)]
2351#[serde(rename_all = "camelCase")]
2352pub struct NetworkPerson {
2353    pub id: String,
2354    #[serde(default)]
2355    pub full_name: String,
2356    pub first_name: Option<String>,
2357    pub last_name: Option<String>,
2358}
2359
2360/// A company in a person's network.
2361#[derive(Debug, Clone, Deserialize)]
2362#[serde(rename_all = "camelCase")]
2363pub struct NetworkCompany {
2364    #[serde(default)]
2365    pub uid: String,
2366    pub name: Option<String>,
2367    #[serde(default)]
2368    pub role: String,
2369    #[serde(default)]
2370    pub role_category: String,
2371    pub is_current: Option<bool>,
2372    pub since: Option<String>,
2373    pub until: Option<String>,
2374    // Enrichment provenance (v3.1+)
2375    pub role_source: Option<String>,
2376    pub role_confidence: Option<f32>,
2377    pub role_inferred_at: Option<String>,
2378}
2379
2380/// A company shared between a person and a co-director.
2381#[derive(Debug, Clone, Deserialize)]
2382#[serde(rename_all = "camelCase")]
2383pub struct CoDirectorCompany {
2384    #[serde(default)]
2385    pub uid: String,
2386    pub name: Option<String>,
2387}
2388
2389/// A person who shares company directorships with the primary person.
2390#[derive(Debug, Clone, Deserialize)]
2391#[serde(rename_all = "camelCase")]
2392pub struct CoDirector {
2393    #[serde(default)]
2394    pub person_id: String,
2395    #[serde(default)]
2396    pub name: String,
2397    #[serde(default)]
2398    pub shared_companies: i64,
2399    #[serde(default)]
2400    pub companies: Vec<CoDirectorCompany>,
2401}
2402
2403/// Aggregate statistics for a person's network.
2404#[derive(Debug, Clone, Deserialize)]
2405#[serde(rename_all = "camelCase")]
2406pub struct NetworkStats {
2407    #[serde(default)]
2408    pub total_companies: i64,
2409    #[serde(default)]
2410    pub active_roles: i64,
2411    #[serde(default)]
2412    pub co_director_count: i64,
2413}
2414
2415/// Response for a person-centric network view.
2416#[derive(Debug, Clone, Deserialize)]
2417#[serde(rename_all = "camelCase")]
2418pub struct PersonNetworkResponse {
2419    pub person: NetworkPerson,
2420    #[serde(default)]
2421    pub companies: Vec<NetworkCompany>,
2422    #[serde(default)]
2423    pub co_directors: Vec<CoDirector>,
2424    pub stats: NetworkStats,
2425}
2426
2427/// Query parameters for `persons.board_members()`.
2428#[derive(Debug, Clone, Default, Serialize)]
2429pub struct BoardMemberParams {
2430    #[serde(skip_serializing_if = "Option::is_none")]
2431    pub page: Option<i64>,
2432    #[serde(skip_serializing_if = "Option::is_none", rename = "pageSize")]
2433    pub page_size: Option<i64>,
2434}
2435
2436// ---------------------------------------------------------------------------
2437// Enriched watchlist entry (v3.1+)
2438// ---------------------------------------------------------------------------
2439
2440/// An enriched company entry in a watchlist response.
2441#[derive(Debug, Clone, Deserialize)]
2442#[serde(rename_all = "camelCase")]
2443pub struct WatchlistCompanyEntry {
2444    pub uid: String,
2445    pub name: Option<String>,
2446    pub status: Option<String>,
2447    pub canton: Option<String>,
2448}
2449
2450// ---------------------------------------------------------------------------
2451// Comparative AI
2452// ---------------------------------------------------------------------------
2453
2454/// Request body for the AI comparative dossier endpoint.
2455#[derive(Debug, Clone, Serialize)]
2456#[serde(rename_all = "camelCase")]
2457pub struct ComparativeRequest {
2458    pub uids: Vec<String>,
2459    #[serde(skip_serializing_if = "Option::is_none")]
2460    pub focus: Option<String>,
2461}
2462
2463/// A company-role entry within a board overlap.
2464#[derive(Debug, Clone, Deserialize)]
2465#[serde(rename_all = "camelCase")]
2466pub struct OverlapCompanyRole {
2467    #[serde(default)]
2468    pub uid: String,
2469    #[serde(default)]
2470    pub company_name: String,
2471    #[serde(default)]
2472    pub role: String,
2473}
2474
2475/// A board member appearing in multiple compared companies.
2476#[derive(Debug, Clone, Deserialize)]
2477#[serde(rename_all = "camelCase")]
2478pub struct BoardOverlap {
2479    #[serde(default)]
2480    pub person_name: String,
2481    #[serde(default)]
2482    pub companies: Vec<OverlapCompanyRole>,
2483}
2484
2485/// An auditor serving one or more compared companies.
2486#[derive(Debug, Clone, Deserialize)]
2487#[serde(rename_all = "camelCase")]
2488pub struct CompAuditorEntry {
2489    #[serde(default)]
2490    pub auditor_name: String,
2491    #[serde(default)]
2492    pub company_count: i64,
2493    #[serde(default)]
2494    pub company_uids: Vec<String>,
2495    #[serde(default)]
2496    pub group_share: f64,
2497}
2498
2499/// Auditor analysis across compared companies.
2500#[derive(Debug, Clone, Deserialize)]
2501#[serde(rename_all = "camelCase")]
2502pub struct AuditorAnalysis {
2503    #[serde(default)]
2504    pub auditor_distribution: Vec<CompAuditorEntry>,
2505    #[serde(default)]
2506    pub unique_auditor_count: i64,
2507    #[serde(default)]
2508    pub concentration_flag: bool,
2509}
2510
2511/// A single governance score factor.
2512#[derive(Debug, Clone, Deserialize)]
2513#[serde(rename_all = "camelCase")]
2514pub struct GovernanceFactor {
2515    #[serde(default)]
2516    pub factor: String,
2517    #[serde(default)]
2518    pub score: i64,
2519    #[serde(default)]
2520    pub description: String,
2521}
2522
2523/// Per-company governance score.
2524#[derive(Debug, Clone, Deserialize)]
2525#[serde(rename_all = "camelCase")]
2526pub struct GovernanceScore {
2527    #[serde(default)]
2528    pub uid: String,
2529    #[serde(default)]
2530    pub company_name: String,
2531    #[serde(default)]
2532    pub score: i64,
2533    #[serde(default)]
2534    pub factors: Vec<GovernanceFactor>,
2535}
2536
2537/// AI-generated comparative dossier for multiple companies.
2538#[derive(Debug, Clone, Deserialize)]
2539#[serde(rename_all = "camelCase")]
2540pub struct ComparativeResponse {
2541    #[serde(default)]
2542    pub uids: Vec<String>,
2543    #[serde(default)]
2544    pub focus: String,
2545    #[serde(default)]
2546    pub report: String,
2547    #[serde(default)]
2548    pub board_overlaps: Vec<BoardOverlap>,
2549    #[serde(default)]
2550    pub auditor_analysis: Option<AuditorAnalysis>,
2551    #[serde(default)]
2552    pub governance_scores: Vec<GovernanceScore>,
2553    #[serde(default)]
2554    pub generated_at: String,
2555}
2556
2557// ---------------------------------------------------------------------------
2558// Predictive Risk
2559// ---------------------------------------------------------------------------
2560
2561/// Request body for the predictive risk scoring endpoint.
2562#[derive(Debug, Clone, Serialize)]
2563#[serde(rename_all = "camelCase")]
2564pub struct PredictiveRiskRequest {
2565    #[serde(skip_serializing_if = "Option::is_none")]
2566    pub lookback_days: Option<i64>,
2567}
2568
2569/// A pre-dissolution risk indicator.
2570#[derive(Debug, Clone, Deserialize)]
2571#[serde(rename_all = "camelCase")]
2572pub struct PredictiveRiskIndicator {
2573    #[serde(default)]
2574    pub signal: String,
2575    #[serde(default)]
2576    pub triggered: bool,
2577    #[serde(default)]
2578    pub weight: f64,
2579    #[serde(default)]
2580    pub contribution: f64,
2581    #[serde(default)]
2582    pub severity: String,
2583    #[serde(default)]
2584    pub description: String,
2585}
2586
2587/// Predictive risk scoring response with dissolution probability.
2588#[derive(Debug, Clone, Deserialize)]
2589#[serde(rename_all = "camelCase")]
2590pub struct PredictiveRiskResponse {
2591    #[serde(default)]
2592    pub uid: String,
2593    #[serde(default)]
2594    pub company_name: String,
2595    #[serde(default)]
2596    pub dissolution_probability: f64,
2597    #[serde(default)]
2598    pub risk_level: String,
2599    #[serde(default)]
2600    pub pre_dissolution_indicators: Vec<PredictiveRiskIndicator>,
2601    #[serde(default)]
2602    pub credit_risk_score: i64,
2603    #[serde(default)]
2604    pub recommendation: String,
2605    #[serde(default)]
2606    pub assessed_at: String,
2607}
2608
2609// ---------------------------------------------------------------------------
2610// PDF Profile
2611// ---------------------------------------------------------------------------
2612
2613/// A board member entry in a PDF profile response.
2614#[derive(Debug, Clone, Deserialize)]
2615#[serde(rename_all = "camelCase")]
2616pub struct PdfBoardMember {
2617    #[serde(default)]
2618    pub first_name: Option<String>,
2619    #[serde(default)]
2620    pub last_name: Option<String>,
2621    #[serde(default)]
2622    pub role: String,
2623    #[serde(default)]
2624    pub signing_authority: Option<String>,
2625    #[serde(default)]
2626    pub since: Option<String>,
2627    #[serde(default)]
2628    pub until: Option<String>,
2629}
2630
2631/// A company event in a PDF profile response.
2632#[derive(Debug, Clone, Deserialize)]
2633#[serde(rename_all = "camelCase")]
2634pub struct PdfEvent {
2635    #[serde(default)]
2636    pub id: String,
2637    #[serde(default)]
2638    pub category: String,
2639    #[serde(default)]
2640    pub summary: String,
2641    #[serde(default)]
2642    pub severity: String,
2643    #[serde(default)]
2644    pub detected_at: String,
2645    #[serde(default)]
2646    pub source_date: Option<String>,
2647}
2648
2649/// An auditor tenure entry in a PDF profile response.
2650#[derive(Debug, Clone, Deserialize)]
2651#[serde(rename_all = "camelCase")]
2652pub struct PdfAuditorTenure {
2653    #[serde(default)]
2654    pub auditor_name: String,
2655    #[serde(default)]
2656    pub appointed_at: Option<String>,
2657    #[serde(default)]
2658    pub resigned_at: Option<String>,
2659    #[serde(default)]
2660    pub tenure_years: Option<f64>,
2661    #[serde(default)]
2662    pub is_current: bool,
2663}
2664
2665/// Core company data within a PDF profile response.
2666#[derive(Debug, Clone, Deserialize)]
2667#[serde(rename_all = "camelCase")]
2668pub struct PdfCompanyData {
2669    #[serde(default)]
2670    pub uid: String,
2671    #[serde(default)]
2672    pub name: String,
2673    #[serde(default)]
2674    pub canton: Option<String>,
2675    #[serde(default)]
2676    pub status: Option<String>,
2677    #[serde(default)]
2678    pub legal_form: Option<String>,
2679    #[serde(default)]
2680    pub share_capital: Option<f64>,
2681    #[serde(default)]
2682    pub currency: Option<String>,
2683    #[serde(default)]
2684    pub purpose: Option<String>,
2685    #[serde(default)]
2686    pub founding_date: Option<String>,
2687    #[serde(default)]
2688    pub registration_date: Option<String>,
2689    #[serde(default)]
2690    pub legal_seat: Option<String>,
2691    #[serde(default)]
2692    pub municipality: Option<String>,
2693    #[serde(default)]
2694    pub address_street: Option<String>,
2695    #[serde(default)]
2696    pub address_house_number: Option<String>,
2697    #[serde(default)]
2698    pub address_zip_code: Option<String>,
2699    #[serde(default)]
2700    pub address_city: Option<String>,
2701    #[serde(default)]
2702    pub website: Option<String>,
2703    #[serde(default)]
2704    pub industry: Option<String>,
2705    #[serde(default)]
2706    pub sub_industry: Option<String>,
2707    #[serde(default)]
2708    pub employee_count: Option<i32>,
2709    #[serde(default)]
2710    pub auditor_name: Option<String>,
2711    #[serde(default)]
2712    pub auditor_category: Option<String>,
2713    #[serde(default)]
2714    pub sanctions_hit: Option<bool>,
2715    #[serde(default)]
2716    pub is_finma_regulated: Option<bool>,
2717    #[serde(default)]
2718    pub old_names: Option<Vec<String>>,
2719    #[serde(default)]
2720    pub translations: Option<Vec<String>>,
2721}
2722
2723/// Structured company profile data suitable for PDF rendering.
2724#[derive(Debug, Clone, Deserialize)]
2725#[serde(rename_all = "camelCase")]
2726pub struct PdfProfileResponse {
2727    pub company: PdfCompanyData,
2728    #[serde(default)]
2729    pub board_members: Vec<PdfBoardMember>,
2730    #[serde(default)]
2731    pub recent_events: Vec<PdfEvent>,
2732    #[serde(default)]
2733    pub auditor_history: Vec<PdfAuditorTenure>,
2734    #[serde(default)]
2735    pub generated_at: String,
2736}
2737
2738// ---------------------------------------------------------------------------
2739// Reports (Industry)
2740// ---------------------------------------------------------------------------
2741
2742/// Summary of an industry with company count.
2743#[derive(Debug, Clone, Deserialize)]
2744#[serde(rename_all = "camelCase")]
2745pub struct IndustrySummary {
2746    #[serde(default)]
2747    pub industry: String,
2748    #[serde(default)]
2749    pub company_count: i64,
2750}
2751
2752/// List of available industries.
2753#[derive(Debug, Clone, Deserialize)]
2754#[serde(rename_all = "camelCase")]
2755pub struct IndustryListResponse {
2756    #[serde(default)]
2757    pub industries: Vec<IndustrySummary>,
2758    #[serde(default)]
2759    pub total: i64,
2760}
2761
2762/// A company entry within an industry report.
2763#[derive(Debug, Clone, Deserialize)]
2764#[serde(rename_all = "camelCase")]
2765pub struct IndustryCompanyEntry {
2766    #[serde(default)]
2767    pub uid: String,
2768    #[serde(default)]
2769    pub name: String,
2770    #[serde(default)]
2771    pub canton: Option<String>,
2772    #[serde(default)]
2773    pub share_capital: Option<f64>,
2774    #[serde(default)]
2775    pub status: Option<String>,
2776}
2777
2778/// Canton distribution entry in a report.
2779#[derive(Debug, Clone, Deserialize)]
2780#[serde(rename_all = "camelCase")]
2781pub struct ReportCantonCount {
2782    #[serde(default)]
2783    pub canton: String,
2784    #[serde(default)]
2785    pub count: i64,
2786}
2787
2788/// Auditor concentration entry in a report.
2789#[derive(Debug, Clone, Deserialize)]
2790#[serde(rename_all = "camelCase")]
2791pub struct ReportAuditorCount {
2792    #[serde(default)]
2793    pub auditor_name: String,
2794    #[serde(default)]
2795    pub count: i64,
2796}
2797
2798/// Status distribution entry.
2799#[derive(Debug, Clone, Deserialize)]
2800#[serde(rename_all = "camelCase")]
2801pub struct StatusCount {
2802    #[serde(default)]
2803    pub status: String,
2804    #[serde(default)]
2805    pub count: i64,
2806}
2807
2808/// Detailed industry report with analytics.
2809#[derive(Debug, Clone, Deserialize)]
2810#[serde(rename_all = "camelCase")]
2811pub struct IndustryReportResponse {
2812    #[serde(default)]
2813    pub industry: String,
2814    #[serde(default)]
2815    pub company_count: i64,
2816    #[serde(default)]
2817    pub avg_capital: Option<f64>,
2818    #[serde(default)]
2819    pub median_capital: Option<f64>,
2820    #[serde(default)]
2821    pub top_companies: Vec<IndustryCompanyEntry>,
2822    #[serde(default)]
2823    pub canton_distribution: Vec<ReportCantonCount>,
2824    #[serde(default)]
2825    pub recent_changes: i64,
2826    #[serde(default)]
2827    pub auditor_concentration: Vec<ReportAuditorCount>,
2828    #[serde(default)]
2829    pub status_distribution: Vec<StatusCount>,
2830    #[serde(default)]
2831    pub generated_at: String,
2832}
2833
2834/// AI-generated industry narrative report.
2835#[derive(Debug, Clone, Deserialize)]
2836#[serde(rename_all = "camelCase")]
2837pub struct GeneratedIndustryReport {
2838    #[serde(default)]
2839    pub industry: String,
2840    #[serde(default)]
2841    pub report: String,
2842    #[serde(default)]
2843    pub sources: Vec<String>,
2844    #[serde(default)]
2845    pub generated_at: String,
2846}
2847
2848// ---------------------------------------------------------------------------
2849// Sanctions
2850// ---------------------------------------------------------------------------
2851
2852/// Query parameters for browsing the sanctions database.
2853#[derive(Debug, Clone, Default, Serialize)]
2854#[serde(rename_all = "camelCase")]
2855pub struct SanctionsSearchParams {
2856    #[serde(skip_serializing_if = "Option::is_none")]
2857    pub search: Option<String>,
2858    #[serde(skip_serializing_if = "Option::is_none")]
2859    pub entity_type: Option<String>,
2860    #[serde(skip_serializing_if = "Option::is_none")]
2861    pub program: Option<String>,
2862    #[serde(skip_serializing_if = "Option::is_none")]
2863    pub page: Option<i64>,
2864    #[serde(skip_serializing_if = "Option::is_none", rename = "pageSize")]
2865    pub page_size: Option<i64>,
2866}
2867
2868/// A single sanctions list entry.
2869#[derive(Debug, Clone, Deserialize)]
2870#[serde(rename_all = "camelCase")]
2871pub struct SanctionEntry {
2872    #[serde(default)]
2873    pub seco_id: String,
2874    #[serde(default)]
2875    pub entity_type: String,
2876    #[serde(default)]
2877    pub name: String,
2878    #[serde(default)]
2879    pub aliases: Vec<String>,
2880    #[serde(default)]
2881    pub nationality: Option<String>,
2882    #[serde(default)]
2883    pub date_of_birth: Option<String>,
2884    #[serde(default)]
2885    pub address: Option<String>,
2886    #[serde(default)]
2887    pub program: String,
2888    #[serde(default)]
2889    pub listed_since: Option<String>,
2890    #[serde(default)]
2891    pub source_url: String,
2892}
2893
2894/// Paginated sanctions browse response.
2895#[derive(Debug, Clone, Deserialize)]
2896#[serde(rename_all = "camelCase")]
2897pub struct SanctionsListResponse {
2898    #[serde(default)]
2899    pub items: Vec<SanctionEntry>,
2900    #[serde(default)]
2901    pub total: i64,
2902    #[serde(default)]
2903    pub page: i64,
2904    #[serde(default)]
2905    pub page_size: i64,
2906}
2907
2908// ---------------------------------------------------------------------------
2909// Pipelines
2910// ---------------------------------------------------------------------------
2911
2912/// A sales/tracking pipeline.
2913#[derive(Debug, Clone, Deserialize)]
2914#[serde(rename_all = "camelCase")]
2915pub struct Pipeline {
2916    #[serde(default)]
2917    pub id: String,
2918    #[serde(default)]
2919    pub team_id: String,
2920    #[serde(default)]
2921    pub name: String,
2922    #[serde(default)]
2923    pub stages: Vec<String>,
2924    #[serde(default)]
2925    pub created_at: i64,
2926}
2927
2928/// An entry (company) within a pipeline stage.
2929#[derive(Debug, Clone, Deserialize)]
2930#[serde(rename_all = "camelCase")]
2931pub struct PipelineEntry {
2932    #[serde(default)]
2933    pub id: String,
2934    #[serde(default)]
2935    pub pipeline_id: String,
2936    #[serde(default)]
2937    pub company_uid: String,
2938    #[serde(default)]
2939    pub company_name: String,
2940    #[serde(default)]
2941    pub canton: Option<String>,
2942    #[serde(default)]
2943    pub stage: String,
2944    #[serde(default)]
2945    pub assigned_to_user_id: Option<String>,
2946    #[serde(default)]
2947    pub assigned_to_name: Option<String>,
2948    #[serde(default)]
2949    pub tier: i64,
2950    #[serde(default)]
2951    pub score: Option<f64>,
2952    #[serde(default)]
2953    pub notes: Option<String>,
2954    #[serde(default)]
2955    pub created_at: i64,
2956    #[serde(default)]
2957    pub updated_at: i64,
2958}
2959
2960/// A pipeline with its entries loaded.
2961#[derive(Debug, Clone, Deserialize)]
2962#[serde(rename_all = "camelCase")]
2963pub struct PipelineWithEntries {
2964    #[serde(default)]
2965    pub id: String,
2966    #[serde(default)]
2967    pub team_id: String,
2968    #[serde(default)]
2969    pub name: String,
2970    #[serde(default)]
2971    pub stages: Vec<String>,
2972    #[serde(default)]
2973    pub created_at: i64,
2974    #[serde(default)]
2975    pub entries: Vec<PipelineEntry>,
2976    #[serde(default)]
2977    pub total_entries: i64,
2978}
2979
2980/// Aggregate statistics for a pipeline.
2981#[derive(Debug, Clone, Deserialize)]
2982#[serde(rename_all = "camelCase")]
2983pub struct PipelineStats {
2984    #[serde(default)]
2985    pub by_stage: HashMap<String, i64>,
2986    #[serde(default)]
2987    pub by_tier: HashMap<String, i64>,
2988    #[serde(default)]
2989    pub total: i64,
2990}
2991
2992/// Request body for creating a pipeline.
2993#[derive(Debug, Clone, Serialize)]
2994#[serde(rename_all = "camelCase")]
2995pub struct CreatePipelineRequest {
2996    pub name: String,
2997    #[serde(skip_serializing_if = "Option::is_none")]
2998    pub stages: Option<Vec<String>>,
2999}
3000
3001/// Request body for adding an entry to a pipeline.
3002#[derive(Debug, Clone, Serialize)]
3003#[serde(rename_all = "camelCase")]
3004pub struct AddEntryRequest {
3005    pub company_uid: String,
3006    #[serde(skip_serializing_if = "Option::is_none")]
3007    pub stage: Option<String>,
3008    #[serde(skip_serializing_if = "Option::is_none")]
3009    pub tier: Option<i64>,
3010    #[serde(skip_serializing_if = "Option::is_none")]
3011    pub assigned_to_user_id: Option<String>,
3012}
3013
3014/// Request body for updating a pipeline entry.
3015#[derive(Debug, Clone, Default, Serialize)]
3016#[serde(rename_all = "camelCase")]
3017pub struct UpdateEntryRequest {
3018    #[serde(skip_serializing_if = "Option::is_none")]
3019    pub stage: Option<String>,
3020    #[serde(skip_serializing_if = "Option::is_none")]
3021    pub tier: Option<i64>,
3022    #[serde(skip_serializing_if = "Option::is_none")]
3023    pub assigned_to_user_id: Option<String>,
3024    #[serde(skip_serializing_if = "Option::is_none")]
3025    pub notes: Option<String>,
3026}
3027
3028// ---------------------------------------------------------------------------
3029// Saved Searches
3030// ---------------------------------------------------------------------------
3031
3032/// A saved search query that can be scheduled or linked to alerts.
3033#[derive(Debug, Clone, Deserialize)]
3034#[serde(rename_all = "camelCase")]
3035pub struct SavedSearch {
3036    #[serde(default)]
3037    pub id: String,
3038    #[serde(default)]
3039    pub name: String,
3040    #[serde(default)]
3041    pub description: Option<String>,
3042    #[serde(default)]
3043    pub search_params: Option<serde_json::Value>,
3044    #[serde(default)]
3045    pub is_scheduled: bool,
3046    #[serde(default)]
3047    pub schedule_frequency: Option<String>,
3048    #[serde(default)]
3049    pub last_run_at: Option<String>,
3050    #[serde(default)]
3051    pub last_result_count: Option<i64>,
3052    #[serde(default)]
3053    pub created_at: String,
3054    #[serde(default)]
3055    pub updated_at: String,
3056}
3057
3058/// Request body for creating a saved search.
3059#[derive(Debug, Clone, Serialize)]
3060#[serde(rename_all = "camelCase")]
3061pub struct CreateSavedSearchRequest {
3062    pub name: String,
3063    pub search_params: serde_json::Value,
3064    #[serde(skip_serializing_if = "Option::is_none")]
3065    pub description: Option<String>,
3066    #[serde(default)]
3067    pub is_scheduled: bool,
3068    #[serde(skip_serializing_if = "Option::is_none")]
3069    pub schedule_frequency: Option<String>,
3070}
3071
3072/// Request body for updating a saved search.
3073#[derive(Debug, Clone, Default, Serialize)]
3074#[serde(rename_all = "camelCase")]
3075pub struct UpdateSavedSearchRequest {
3076    #[serde(skip_serializing_if = "Option::is_none")]
3077    pub name: Option<String>,
3078    #[serde(skip_serializing_if = "Option::is_none")]
3079    pub description: Option<String>,
3080    #[serde(skip_serializing_if = "Option::is_none")]
3081    pub search_params: Option<serde_json::Value>,
3082    #[serde(skip_serializing_if = "Option::is_none")]
3083    pub is_scheduled: Option<bool>,
3084    #[serde(skip_serializing_if = "Option::is_none")]
3085    pub schedule_frequency: Option<String>,
3086}
3087
3088// ---------------------------------------------------------------------------
3089// Company Diff
3090// ---------------------------------------------------------------------------
3091
3092/// A single field-level change in a company diff.
3093#[derive(Debug, Clone, Deserialize)]
3094#[serde(rename_all = "camelCase")]
3095pub struct DiffEntry {
3096    pub field: String,
3097    #[serde(default)]
3098    pub from: Option<String>,
3099    #[serde(default)]
3100    pub to: Option<String>,
3101    #[serde(default)]
3102    pub changed_at: String,
3103    #[serde(default)]
3104    pub change_type: String,
3105}
3106
3107/// Response for a company diff request.
3108#[derive(Debug, Clone, Deserialize)]
3109#[serde(rename_all = "camelCase")]
3110pub struct CompanyDiffResponse {
3111    pub uid: String,
3112    pub since: String,
3113    pub until: String,
3114    #[serde(default)]
3115    pub changes: Vec<DiffEntry>,
3116    #[serde(default)]
3117    pub total_changes: i64,
3118}
3119
3120// ---------------------------------------------------------------------------
3121// Bulk Profiles Export
3122// ---------------------------------------------------------------------------
3123
3124/// Request body for bulk profile export.
3125#[derive(Debug, Clone, Default, Serialize)]
3126#[serde(rename_all = "camelCase")]
3127pub struct BulkProfilesRequest {
3128    pub uids: Vec<String>,
3129    #[serde(skip_serializing_if = "Option::is_none")]
3130    pub include_timeline: Option<bool>,
3131    #[serde(skip_serializing_if = "Option::is_none")]
3132    pub include_board: Option<bool>,
3133}
3134
3135// ---------------------------------------------------------------------------
3136// Clone derives for newly-introduced request types (ergonomics)
3137// ---------------------------------------------------------------------------