boarddata 6.7.3__tar.gz → 6.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {boarddata-6.7.3 → boarddata-6.7.4}/PKG-INFO +1 -1
  2. {boarddata-6.7.3 → boarddata-6.7.4}/__init__.py +1 -1
  3. {boarddata-6.7.3 → boarddata-6.7.4}/_comex.py +66 -16
  4. {boarddata-6.7.3 → boarddata-6.7.4}/_committees.py +1 -3
  5. {boarddata-6.7.3 → boarddata-6.7.4}/_directors.py +4 -4
  6. {boarddata-6.7.3 → boarddata-6.7.4}/_utilities.py +3 -2
  7. {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/PKG-INFO +1 -1
  8. {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/SOURCES.txt +2 -0
  9. {boarddata-6.7.3 → boarddata-6.7.4}/pyproject.toml +1 -1
  10. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_comex.py +114 -4
  11. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_directors.py +2 -2
  12. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_exports.py +4 -4
  13. boarddata-6.7.4/tests/test_politic_fixture_conformance.py +116 -0
  14. boarddata-6.7.4/tests/test_types_politic.py +398 -0
  15. {boarddata-6.7.3 → boarddata-6.7.4}/types/__init__.py +26 -0
  16. {boarddata-6.7.3 → boarddata-6.7.4}/types/auditors.py +3 -3
  17. {boarddata-6.7.3 → boarddata-6.7.4}/types/comex.py +231 -10
  18. boarddata-6.7.4/types/core.py +172 -0
  19. {boarddata-6.7.3 → boarddata-6.7.4}/types/directors.py +2 -2
  20. boarddata-6.7.3/types/core.py +0 -97
  21. {boarddata-6.7.3 → boarddata-6.7.4}/CLAUDE.md +0 -0
  22. {boarddata-6.7.3 → boarddata-6.7.4}/README.md +0 -0
  23. {boarddata-6.7.3 → boarddata-6.7.4}/_assemblies.py +0 -0
  24. {boarddata-6.7.3 → boarddata-6.7.4}/_auditors.py +0 -0
  25. {boarddata-6.7.3 → boarddata-6.7.4}/_base.py +0 -0
  26. {boarddata-6.7.3 → boarddata-6.7.4}/_companies.py +0 -0
  27. {boarddata-6.7.3 → boarddata-6.7.4}/_criteria.py +0 -0
  28. {boarddata-6.7.3 → boarddata-6.7.4}/_documents.py +0 -0
  29. {boarddata-6.7.3 → boarddata-6.7.4}/_esg.py +0 -0
  30. {boarddata-6.7.3 → boarddata-6.7.4}/_indexes.py +0 -0
  31. {boarddata-6.7.3 → boarddata-6.7.4}/_persons.py +0 -0
  32. {boarddata-6.7.3 → boarddata-6.7.4}/_risks.py +0 -0
  33. {boarddata-6.7.3 → boarddata-6.7.4}/_sentinel.py +0 -0
  34. {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/dependency_links.txt +0 -0
  35. {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/requires.txt +0 -0
  36. {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/top_level.txt +0 -0
  37. {boarddata-6.7.3 → boarddata-6.7.4}/cache.py +0 -0
  38. {boarddata-6.7.3 → boarddata-6.7.4}/client.py +1 -1
  39. {boarddata-6.7.3 → boarddata-6.7.4}/dq/__init__.py +0 -0
  40. {boarddata-6.7.3 → boarddata-6.7.4}/dq/adjudicate.py +0 -0
  41. {boarddata-6.7.3 → boarddata-6.7.4}/dq/apply.py +0 -0
  42. {boarddata-6.7.3 → boarddata-6.7.4}/dq/context.py +0 -0
  43. {boarddata-6.7.3 → boarddata-6.7.4}/dq/db.py +0 -0
  44. {boarddata-6.7.3 → boarddata-6.7.4}/dq/domains.py +0 -0
  45. {boarddata-6.7.3 → boarddata-6.7.4}/dq/models.py +0 -0
  46. {boarddata-6.7.3 → boarddata-6.7.4}/dq/oracle.py +0 -0
  47. {boarddata-6.7.3 → boarddata-6.7.4}/dq/registry.py +0 -0
  48. {boarddata-6.7.3 → boarddata-6.7.4}/dq/report.py +0 -0
  49. {boarddata-6.7.3 → boarddata-6.7.4}/dq/rules/__init__.py +0 -0
  50. {boarddata-6.7.3 → boarddata-6.7.4}/dq/rules/cross_company.py +0 -0
  51. {boarddata-6.7.3 → boarddata-6.7.4}/dq/rules/lexical.py +0 -0
  52. {boarddata-6.7.3 → boarddata-6.7.4}/dq/s3.py +0 -0
  53. {boarddata-6.7.3 → boarddata-6.7.4}/dq/verify_s3.py +0 -0
  54. {boarddata-6.7.3 → boarddata-6.7.4}/errors.py +0 -0
  55. {boarddata-6.7.3 → boarddata-6.7.4}/py.typed +0 -0
  56. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/__init__.py +0 -0
  57. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_apply.py +0 -0
  58. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_batch.py +0 -0
  59. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_plan.py +0 -0
  60. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_revert.py +0 -0
  61. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_scan.py +0 -0
  62. {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_verify_s3.py +0 -0
  63. {boarddata-6.7.3 → boarddata-6.7.4}/setup.cfg +0 -0
  64. {boarddata-6.7.3 → boarddata-6.7.4}/tests/__init__.py +0 -0
  65. {boarddata-6.7.3 → boarddata-6.7.4}/tests/conftest.py +0 -0
  66. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_assemblies.py +0 -0
  67. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_auditors.py +0 -0
  68. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_base.py +0 -0
  69. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_build_payload.py +0 -0
  70. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_cache.py +0 -0
  71. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_committees.py +0 -0
  72. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_companies.py +0 -0
  73. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_config.py +0 -0
  74. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_criteria.py +0 -0
  75. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_documents.py +0 -0
  76. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_esg.py +0 -0
  77. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_indexes.py +0 -0
  78. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_persons.py +0 -0
  79. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_risks.py +0 -0
  80. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_sentinel.py +0 -0
  81. {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_utilities.py +0 -0
  82. {boarddata-6.7.3 → boarddata-6.7.4}/types/assemblies.py +0 -0
  83. {boarddata-6.7.3 → boarddata-6.7.4}/types/committees.py +0 -0
  84. {boarddata-6.7.3 → boarddata-6.7.4}/types/companies.py +0 -0
  85. {boarddata-6.7.3 → boarddata-6.7.4}/types/criteria.py +0 -0
  86. {boarddata-6.7.3 → boarddata-6.7.4}/types/documents.py +0 -0
  87. {boarddata-6.7.3 → boarddata-6.7.4}/types/esg.py +0 -0
  88. {boarddata-6.7.3 → boarddata-6.7.4}/types/indexes.py +0 -0
  89. {boarddata-6.7.3 → boarddata-6.7.4}/types/persons.py +0 -0
  90. {boarddata-6.7.3 → boarddata-6.7.4}/types/risks.py +0 -0
  91. {boarddata-6.7.3 → boarddata-6.7.4}/types/sentinel.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boarddata
3
- Version: 6.7.3
3
+ Version: 6.7.4
4
4
  Summary: Python SDK for the BoardData V2 REST API
5
5
  License: MIT
6
6
  Classifier: Development Status :: 4 - Beta
@@ -6,7 +6,7 @@ from .cache import FileTokenCache, ScalewaySecretTokenCache
6
6
  from .client import BoardDataClient
7
7
  from .errors import BoardDataError, ExportTooLargeError
8
8
 
9
- __version__ = "6.7.3"
9
+ __version__ = "6.7.4"
10
10
  __all__ = [
11
11
  "Base",
12
12
  "BoardDataClient",
@@ -5,7 +5,14 @@ from __future__ import annotations
5
5
  import logging
6
6
  from typing import Any
7
7
 
8
- from .types.comex import ComexCompensationDetail, ComexDetail, ComexExportItem
8
+ from .types.comex import (
9
+ ComexCompensationDetail,
10
+ ComexDetail,
11
+ ComexExportItem,
12
+ ComponentAchievementRateWritePayload,
13
+ CriterionWritePayload,
14
+ Nature,
15
+ )
9
16
  from .types.core import FieldSourcePayload, PaginatedResponse, UpsertResult
10
17
 
11
18
  logger = logging.getLogger("boarddata")
@@ -50,7 +57,7 @@ class ComexMixin:
50
57
  date_from: Filter mandates valid from this date.
51
58
  date_to: Filter mandates valid to this date.
52
59
  as_of: Show mandates active on this date.
53
- nature: Filter by compensation nature (``"REAL"`` or ``"POLITIC"``).
60
+ nature: Filter by compensation nature (``"real"`` or ``"politic"``).
54
61
  roles: Filter by role identifiers (e.g. ``["CEO", "CFO"]``).
55
62
  role_at: Filter for mandates that held a role at this date (``"YYYY-MM-DD"``).
56
63
  page: Page number (1-indexed).
@@ -231,7 +238,7 @@ class ComexMixin:
231
238
  date_from: Filter mandates valid from this date (``"YYYY-MM-DD"``).
232
239
  date_to: Filter mandates valid to this date (``"YYYY-MM-DD"``).
233
240
  as_of: Show mandates active on this date (``"YYYY-MM-DD"``).
234
- nature: Filter by compensation nature (``"REAL"`` or ``"POLITIC"``).
241
+ nature: Filter by compensation nature (``"real"`` or ``"politic"``).
235
242
  **kwargs: Advanced filters forwarded as query params.
236
243
 
237
244
  Returns:
@@ -310,7 +317,7 @@ class ComexMixin:
310
317
  self,
311
318
  comex_uid: str,
312
319
  fiscal_year: int,
313
- nature: str,
320
+ nature: Nature,
314
321
  *,
315
322
  assembly: str | None = None,
316
323
  fixed: str | None = None,
@@ -350,21 +357,26 @@ class ComexMixin:
350
357
  subsidiary_remuneration: str | None = None,
351
358
  remuneration_via_holding: bool | None = None,
352
359
  sti_basis: str | None = None,
360
+ variable_max: str | None = None,
361
+ variable_target_pct: str | None = None,
362
+ variable_max_pct: str | None = None,
353
363
  variable_target_value: str | None = None,
354
364
  variable_max_value: str | None = None,
365
+ reference_amount: str | None = None,
355
366
  lti_grant_cap_basis: str | None = None,
356
367
  lti_grant_cap_value: str | None = None,
368
+ policy_changed_midyear: bool | None = None,
357
369
  currency: str | None = None,
358
370
  field_sources: list[FieldSourcePayload] | None = None,
359
- criteria: list[dict[str, Any]] | None = None,
360
- component_achievement_rates: list[dict[str, Any]] | None = None,
371
+ criteria: list[CriterionWritePayload] | None = None,
372
+ component_achievement_rates: list[ComponentAchievementRateWritePayload] | None = None,
361
373
  ) -> ComexCompensationDetail:
362
374
  """Create a compensation entry for a comex mandate.
363
375
 
364
376
  Args:
365
377
  comex_uid: Comex mandate UUID.
366
378
  fiscal_year: Fiscal year.
367
- nature: Compensation nature (``"REAL"`` or ``"POLITIC"``).
379
+ nature: Compensation nature (``"real"`` or ``"politic"``).
368
380
  assembly: Assembly UUID.
369
381
  fixed: Fixed compensation (decimal).
370
382
  variable: Variable compensation (decimal).
@@ -403,15 +415,26 @@ class ComexMixin:
403
415
  subsidiary_remuneration: Subsidiary remuneration (decimal).
404
416
  remuneration_via_holding: Whether remuneration is provided via holding.
405
417
  sti_basis: Disclosed STI base form (frozen enum, e.g. ``"pct_of_fixed"``).
418
+ variable_max: STI cap in EUR (decimal string; always €, never
419
+ disclosed-basis — the disclosed-basis cap goes in
420
+ ``variable_max_value``).
421
+ variable_target_pct: STI target normalized to % of fixed (decimal
422
+ string; stated or derivation-backed, never back-solved).
423
+ variable_max_pct: STI cap normalized to % of fixed (decimal string).
406
424
  variable_target_value: STI target in the DISCLOSED basis (decimal string;
407
425
  interpretation governed by ``sti_basis`` — never converted).
408
426
  variable_max_value: STI cap in the DISCLOSED basis (decimal string).
427
+ reference_amount: STI reference base amount in EUR (decimal string;
428
+ ``sti_basis="pct_of_reference_amount"`` archetype).
409
429
  lti_grant_cap_basis: Disclosed LTI grant-cap basis (frozen enum).
410
430
  lti_grant_cap_value: LTI grant cap in the DISCLOSED basis (decimal string).
431
+ policy_changed_midyear: Policy regime changed mid-year; the stored
432
+ row is the fiscal-year-end regime (D5).
411
433
  currency: Currency code (e.g. ``"EUR"``).
412
434
  field_sources: Document provenance for fields.
413
- criteria: List of compensation criteria dicts.
414
- component_achievement_rates: List of achievement rate dicts.
435
+ criteria: List of nested criteria entries (see ``CriterionWritePayload``).
436
+ component_achievement_rates: List of achievement-rate entries (see
437
+ ``ComponentAchievementRateWritePayload``).
415
438
 
416
439
  Returns:
417
440
  Created ComexCompensationDetail.
@@ -460,10 +483,15 @@ class ComexMixin:
460
483
  subsidiary_remuneration=subsidiary_remuneration,
461
484
  remuneration_via_holding=remuneration_via_holding,
462
485
  sti_basis=sti_basis,
486
+ variable_max=variable_max,
487
+ variable_target_pct=variable_target_pct,
488
+ variable_max_pct=variable_max_pct,
463
489
  variable_target_value=variable_target_value,
464
490
  variable_max_value=variable_max_value,
491
+ reference_amount=reference_amount,
465
492
  lti_grant_cap_basis=lti_grant_cap_basis,
466
493
  lti_grant_cap_value=lti_grant_cap_value,
494
+ policy_changed_midyear=policy_changed_midyear,
467
495
  currency=currency,
468
496
  field_sources=field_sources,
469
497
  criteria=criteria,
@@ -475,7 +503,7 @@ class ComexMixin:
475
503
  self,
476
504
  comex_uid: str,
477
505
  fiscal_year: int,
478
- nature: str,
506
+ nature: Nature,
479
507
  *,
480
508
  assembly: str | None = None,
481
509
  fixed: str | None = None,
@@ -515,21 +543,26 @@ class ComexMixin:
515
543
  subsidiary_remuneration: str | None = None,
516
544
  remuneration_via_holding: bool | None = None,
517
545
  sti_basis: str | None = None,
546
+ variable_max: str | None = None,
547
+ variable_target_pct: str | None = None,
548
+ variable_max_pct: str | None = None,
518
549
  variable_target_value: str | None = None,
519
550
  variable_max_value: str | None = None,
551
+ reference_amount: str | None = None,
520
552
  lti_grant_cap_basis: str | None = None,
521
553
  lti_grant_cap_value: str | None = None,
554
+ policy_changed_midyear: bool | None = None,
522
555
  currency: str | None = None,
523
556
  field_sources: list[FieldSourcePayload] | None = None,
524
- criteria: list[dict[str, Any]] | None = None,
525
- component_achievement_rates: list[dict[str, Any]] | None = None,
557
+ criteria: list[CriterionWritePayload] | None = None,
558
+ component_achievement_rates: list[ComponentAchievementRateWritePayload] | None = None,
526
559
  ) -> dict[str, Any]:
527
560
  """Replace (clean-swap) a compensation entry for a comex mandate.
528
561
 
529
562
  Args:
530
563
  comex_uid: Comex mandate UUID.
531
564
  fiscal_year: Fiscal year.
532
- nature: Compensation nature (``"REAL"`` or ``"POLITIC"``).
565
+ nature: Compensation nature (``"real"`` or ``"politic"``).
533
566
  assembly: Assembly UUID.
534
567
  fixed: Fixed compensation (decimal).
535
568
  variable: Variable compensation (decimal).
@@ -568,18 +601,30 @@ class ComexMixin:
568
601
  subsidiary_remuneration: Subsidiary remuneration (decimal).
569
602
  remuneration_via_holding: Whether remuneration is provided via holding.
570
603
  sti_basis: Disclosed STI base form (frozen enum, e.g. ``"pct_of_fixed"``).
604
+ variable_max: STI cap in EUR (decimal string; always €, never
605
+ disclosed-basis — the disclosed-basis cap goes in
606
+ ``variable_max_value``).
607
+ variable_target_pct: STI target normalized to % of fixed (decimal
608
+ string; stated or derivation-backed, never back-solved).
609
+ variable_max_pct: STI cap normalized to % of fixed (decimal string).
571
610
  variable_target_value: STI target in the DISCLOSED basis (decimal string;
572
611
  interpretation governed by ``sti_basis`` — never converted).
573
612
  variable_max_value: STI cap in the DISCLOSED basis (decimal string).
613
+ reference_amount: STI reference base amount in EUR (decimal string;
614
+ ``sti_basis="pct_of_reference_amount"`` archetype).
574
615
  lti_grant_cap_basis: Disclosed LTI grant-cap basis (frozen enum).
575
616
  lti_grant_cap_value: LTI grant cap in the DISCLOSED basis (decimal string).
617
+ policy_changed_midyear: Policy regime changed mid-year; the stored
618
+ row is the fiscal-year-end regime (D5).
576
619
  currency: Currency code (e.g. ``"EUR"``).
577
620
  field_sources: Document provenance for fields.
578
- criteria: List of compensation criteria dicts.
579
- component_achievement_rates: List of achievement rate dicts.
621
+ criteria: List of nested criteria entries (see ``CriterionWritePayload``).
622
+ component_achievement_rates: List of achievement-rate entries (see
623
+ ``ComponentAchievementRateWritePayload``).
580
624
 
581
625
  Returns:
582
- dict with {"id", "action"} where action is "replaced" (a prior live row existed) or "created" (none existed).
626
+ dict with {"id", "action"} where action is "replaced" (a prior live row
627
+ existed) or "created" (none existed).
583
628
 
584
629
  Raises:
585
630
  BoardDataError: On non-2xx API response.
@@ -625,10 +670,15 @@ class ComexMixin:
625
670
  subsidiary_remuneration=subsidiary_remuneration,
626
671
  remuneration_via_holding=remuneration_via_holding,
627
672
  sti_basis=sti_basis,
673
+ variable_max=variable_max,
674
+ variable_target_pct=variable_target_pct,
675
+ variable_max_pct=variable_max_pct,
628
676
  variable_target_value=variable_target_value,
629
677
  variable_max_value=variable_max_value,
678
+ reference_amount=reference_amount,
630
679
  lti_grant_cap_basis=lti_grant_cap_basis,
631
680
  lti_grant_cap_value=lti_grant_cap_value,
681
+ policy_changed_midyear=policy_changed_midyear,
632
682
  currency=currency,
633
683
  field_sources=field_sources,
634
684
  criteria=criteria,
@@ -6,9 +6,7 @@ from .types.committees import CommitteeBatchItemPayload, CommitteeBatchResultIte
6
6
 
7
7
 
8
8
  class CommitteesMixin:
9
- def batch_create_committees(
10
- self, items: list[CommitteeBatchItemPayload]
11
- ) -> list[CommitteeBatchResultItem]:
9
+ def batch_create_committees(self, items: list[CommitteeBatchItemPayload]) -> list[CommitteeBatchResultItem]:
12
10
  """Batch upsert board committees. Each item is keyed by issuer ISIN;
13
11
  the backend resolves members to existing persons and idempotently
14
12
  upserts Committee + CommitteeMembership. Returns one result per item
@@ -55,7 +55,7 @@ class DirectorMixin:
55
55
  expiring_year: Keep only mandates whose ``valid_to`` falls inside
56
56
  the given calendar year. Strictly excludes mandates with
57
57
  ``valid_to IS NULL`` (still active).
58
- nature: Filter by compensation nature (``"REAL"`` or ``"POLITIC"``).
58
+ nature: Filter by compensation nature (``"real"`` or ``"politic"``).
59
59
  roles: Filter by role identifiers (e.g. ``["Independent", "Chairman"]``).
60
60
  role_at: Filter for mandates that held a role at this date (``"YYYY-MM-DD"``).
61
61
  page: Page number (1-indexed).
@@ -244,7 +244,7 @@ class DirectorMixin:
244
244
  date_to: Filter mandates valid to this date (``"YYYY-MM-DD"``).
245
245
  as_of: Show mandates active on this date (``"YYYY-MM-DD"``).
246
246
  expiring_year: Keep only mandates expiring in the given year.
247
- nature: Filter by compensation nature (``"REAL"`` or ``"POLITIC"``).
247
+ nature: Filter by compensation nature (``"real"`` or ``"politic"``).
248
248
  **kwargs: Advanced filters forwarded as query params.
249
249
 
250
250
  Returns:
@@ -293,7 +293,7 @@ class DirectorMixin:
293
293
  Args:
294
294
  director_uid: Director mandate UUID.
295
295
  fiscal_year: Filter by fiscal year.
296
- nature: Filter by compensation nature (``"REAL"`` or ``"POLITIC"``).
296
+ nature: Filter by compensation nature (``"real"`` or ``"politic"``).
297
297
  page: Page number.
298
298
  page_size: Results per page.
299
299
 
@@ -356,7 +356,7 @@ class DirectorMixin:
356
356
  Args:
357
357
  director_uid: Director mandate UUID.
358
358
  fiscal_year: Fiscal year.
359
- nature: Compensation nature (``"REAL"`` or ``"POLITIC"``).
359
+ nature: Compensation nature (``"real"`` or ``"politic"``).
360
360
  assembly: Assembly UUID.
361
361
  token_personal: Personal attendance tokens (decimal).
362
362
  token_personal_paid: Personal attendance tokens paid (decimal).
@@ -7,6 +7,7 @@ from typing import Any
7
7
  from .types.core import (
8
8
  CreateFieldSourceDisputePayload,
9
9
  FieldSourceDisputeDetail,
10
+ FieldSourceItem,
10
11
  SearchResult,
11
12
  )
12
13
 
@@ -70,7 +71,7 @@ class UtilitiesMixin:
70
71
  object_ids: list[str],
71
72
  *,
72
73
  fields: list[str] | None = None,
73
- ) -> Any:
74
+ ) -> dict[str, dict[str, list[FieldSourceItem]]]:
74
75
  """Batch-fetch field sources for objects of a given content type.
75
76
 
76
77
  Args:
@@ -80,7 +81,7 @@ class UtilitiesMixin:
80
81
  fields: Optional list of field names to filter by.
81
82
 
82
83
  Returns:
83
- Dict mapping ``object_id -> field_name -> list`` of source dicts.
84
+ Dict mapping ``object_id -> field_name -> list`` of FieldSourceItem dicts.
84
85
 
85
86
  Raises:
86
87
  BoardDataError: On non-2xx API response.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boarddata
3
- Version: 6.7.3
3
+ Version: 6.7.4
4
4
  Summary: Python SDK for the BoardData V2 REST API
5
5
  License: MIT
6
6
  Classifier: Development Status :: 4 - Beta
@@ -109,8 +109,10 @@ tests/test_esg.py
109
109
  tests/test_exports.py
110
110
  tests/test_indexes.py
111
111
  tests/test_persons.py
112
+ tests/test_politic_fixture_conformance.py
112
113
  tests/test_risks.py
113
114
  tests/test_sentinel.py
115
+ tests/test_types_politic.py
114
116
  tests/test_utilities.py
115
117
  types/__init__.py
116
118
  types/assemblies.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "boarddata"
7
- version = "6.7.3"
7
+ version = "6.7.4"
8
8
  description = "Python SDK for the BoardData V2 REST API"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -27,7 +27,7 @@ class TestCreateComexCompensation:
27
27
  c.create_comex_compensation(
28
28
  "comex-uuid",
29
29
  2024,
30
- "REAL",
30
+ "real",
31
31
  fixed="500000.00",
32
32
  variable="200000.00",
33
33
  currency="EUR",
@@ -137,7 +137,7 @@ class TestReplaceComexCompensation:
137
137
  result = c.replace_comex_compensation(
138
138
  "comex-uuid",
139
139
  2024,
140
- "REAL",
140
+ "real",
141
141
  fixed="500000.00",
142
142
  variable="200000.00",
143
143
  currency="EUR",
@@ -151,13 +151,13 @@ class TestReplaceComexCompensation:
151
151
  assert payload["variable"] == "200000.00"
152
152
  assert payload["currency"] == "EUR"
153
153
  assert payload["fiscal_year"] == 2024
154
- assert payload["nature"] == "REAL"
154
+ assert payload["nature"] == "real"
155
155
  assert result == {"id": "comp9", "action": "replaced"}
156
156
 
157
157
  def test_returns_created_when_no_prior_row(self):
158
158
  c = make_client()
159
159
  c._request.return_value = {"id": "comp10", "action": "created"}
160
- result = c.replace_comex_compensation("comex-uuid", 2024, "POLITIC", fixed="1.00")
160
+ result = c.replace_comex_compensation("comex-uuid", 2024, "politic", fixed="1.00")
161
161
  assert result["action"] == "created"
162
162
 
163
163
 
@@ -173,10 +173,15 @@ def test_envelope_scalar_kwargs_in_signatures():
173
173
  params = inspect.signature(method).parameters
174
174
  for k in (
175
175
  "sti_basis",
176
+ "variable_max",
177
+ "variable_target_pct",
178
+ "variable_max_pct",
176
179
  "variable_target_value",
177
180
  "variable_max_value",
181
+ "reference_amount",
178
182
  "lti_grant_cap_basis",
179
183
  "lti_grant_cap_value",
184
+ "policy_changed_midyear",
180
185
  ):
181
186
  assert k in params, f"{method.__name__} missing {k}"
182
187
 
@@ -201,3 +206,108 @@ def test_envelope_scalars_reach_the_wire():
201
206
  assert p["variable_max_value"] == "180"
202
207
  assert p["lti_grant_cap_basis"] == "pct_of_fixed"
203
208
  assert p["lti_grant_cap_value"] == "150"
209
+
210
+
211
+ def test_politic_envelope_scalars_reach_the_wire():
212
+ c = make_client()
213
+ c._request.return_value = {"id": "1", "action": "created"}
214
+ c.replace_comex_compensation(
215
+ "uid",
216
+ 2026,
217
+ "politic",
218
+ variable_max="2790000.00",
219
+ variable_target_pct="100.00",
220
+ variable_max_pct="180.00",
221
+ reference_amount="1400000.00",
222
+ policy_changed_midyear=False,
223
+ )
224
+ _, kwargs = c._request.call_args
225
+ p = kwargs["json"]
226
+ assert p["variable_max"] == "2790000.00"
227
+ assert p["variable_target_pct"] == "100.00"
228
+ assert p["variable_max_pct"] == "180.00"
229
+ assert p["reference_amount"] == "1400000.00"
230
+ # False is falsy but NOT None — it must reach the wire (only None is dropped).
231
+ assert p["policy_changed_midyear"] is False
232
+
233
+
234
+ def test_nested_criteria_pass_through_verbatim():
235
+ """Nested entries are passthrough: the SDK must never filter, coerce, or
236
+ rename keys inside criteria / rates / field_sources dicts."""
237
+ c = make_client()
238
+ c._request.return_value = {"id": "1", "action": "created"}
239
+ criterion = {
240
+ "criteria": "cat-uuid",
241
+ "component_type": "variable",
242
+ "weight": "30.00",
243
+ "weight_basis": "share_of_fixed",
244
+ "weight_is_max": True,
245
+ "rate_basis": "points_of_fixed",
246
+ "min_value": "ROE <= 6%",
247
+ "max_value": "ROE >= 15%",
248
+ "payout_curve": {
249
+ "v": 1,
250
+ "curve_type": "linear_ramp",
251
+ "interpolation": "linear",
252
+ "rate_basis": "points_of_fixed",
253
+ "points": [
254
+ {"label": "floor", "metric_numeric": "6", "payout_pct": "0"},
255
+ {"label": "cap", "metric_numeric": "15", "payout_pct": "30"},
256
+ ],
257
+ },
258
+ "disclosure": {"weight": "numeric", "rate_max": "numeric"},
259
+ "field_sources": [
260
+ {
261
+ "document": "doc-uuid",
262
+ "field_name": "amount_at_max",
263
+ "page_number": 424,
264
+ "derivation": {
265
+ "v": 1,
266
+ "status": "derived",
267
+ "formula_display": "€465,000 = 30% × €1,550,000",
268
+ "inputs": [{"name": "fixed", "value": "1550000", "page": 424}],
269
+ },
270
+ }
271
+ ],
272
+ }
273
+ rate = {
274
+ "component_type": "variable",
275
+ "achievement_rate_max": "100.00",
276
+ "payout_curve": {
277
+ "v": 1,
278
+ "curve_type": "rank_scale",
279
+ "interpolation": "none",
280
+ "rate_basis": "points_of_fixed",
281
+ "points": [{"label": "rank_1", "payout_pct": "180"}],
282
+ "overlays": [{"kind": "cap", "factor": "100", "applies_to": "summary"}],
283
+ },
284
+ }
285
+ c.create_comex_compensation(
286
+ "uid",
287
+ 2026,
288
+ "politic",
289
+ criteria=[criterion],
290
+ component_achievement_rates=[rate],
291
+ )
292
+ _, kwargs = c._request.call_args
293
+ p = kwargs["json"]
294
+ assert p["criteria"] == [criterion]
295
+ assert p["component_achievement_rates"] == [rate]
296
+
297
+
298
+ def test_no_uppercase_nature_in_docstrings():
299
+ """The wire is lowercase; docstrings advertising "REAL"/"POLITIC" are the
300
+ latent casing bug fixed EVERYWHERE by the 6.7.0 politic parts (design
301
+ §4.7 / §2 item 9) — comex and directors both."""
302
+ import inspect
303
+
304
+ from boarddata._comex import ComexMixin
305
+ from boarddata._directors import DirectorMixin
306
+
307
+ offenders = []
308
+ for mixin in (ComexMixin, DirectorMixin):
309
+ for name, member in inspect.getmembers(mixin, inspect.isfunction):
310
+ doc = inspect.getdoc(member) or ""
311
+ if '"REAL"' in doc or '"POLITIC"' in doc:
312
+ offenders.append(f"{mixin.__name__}.{name}")
313
+ assert not offenders, offenders
@@ -49,11 +49,11 @@ class TestCreateDirectorCompensation:
49
49
  def test_sends_token_fields(self):
50
50
  c = make_client()
51
51
  c._request.return_value = {"id": "comp1", "fiscal_year": 2024}
52
- c.create_director_compensation("dir-uuid", 2024, "REAL", token_personal="15000.00", currency="EUR")
52
+ c.create_director_compensation("dir-uuid", 2024, "real", token_personal="15000.00", currency="EUR")
53
53
  _, kwargs = c._request.call_args
54
54
  payload = kwargs["json"]
55
55
  assert payload["token_personal"] == "15000.00"
56
- assert payload["nature"] == "REAL"
56
+ assert payload["nature"] == "real"
57
57
  assert "fixed" not in payload # comex field, not board
58
58
 
59
59
 
@@ -152,10 +152,10 @@ class TestExportDirectors:
152
152
  def test_passes_filters(self):
153
153
  c = self._client()
154
154
  with patch("requests.request", return_value=_make_response(200, [])) as mock_req:
155
- c.export_directors(company="comp-uuid", nature="REAL")
155
+ c.export_directors(company="comp-uuid", nature="real")
156
156
  _, kwargs = mock_req.call_args
157
157
  assert kwargs["params"]["company"] == "comp-uuid"
158
- assert kwargs["params"]["nature"] == "REAL"
158
+ assert kwargs["params"]["nature"] == "real"
159
159
 
160
160
  def test_too_large_raises_export_too_large_error(self):
161
161
  c = self._client()
@@ -195,9 +195,9 @@ class TestExportComex:
195
195
  def test_passes_nature_filter(self):
196
196
  c = self._client()
197
197
  with patch("requests.request", return_value=_make_response(200, [])) as mock_req:
198
- c.export_comex(nature="POLITIC")
198
+ c.export_comex(nature="politic")
199
199
  _, kwargs = mock_req.call_args
200
- assert kwargs["params"]["nature"] == "POLITIC"
200
+ assert kwargs["params"]["nature"] == "politic"
201
201
 
202
202
  def test_too_large_raises_export_too_large_error(self):
203
203
  c = self._client()
@@ -0,0 +1,116 @@
1
+ """TypedDict conformance of the nine §7 ground-truth archetype fixtures
2
+ (design §4.8): every key the fixtures send must exist in the SDK's typed
3
+ write vocabulary. Catches SDK/backend drift at the PR gate on either side.
4
+
5
+ Runs only in the monorepo checkout (the fixtures are backend test data);
6
+ skipped in a packaged install.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import inspect
12
+ import json
13
+ from pathlib import Path
14
+
15
+ import pytest
16
+
17
+ FIXTURES = Path(__file__).resolve().parents[2] / "backend/apps/mandates/tests/fixtures/politic_archetypes"
18
+
19
+ pytestmark = pytest.mark.skipif(not FIXTURES.is_dir(), reason="backend fixtures only exist in the monorepo")
20
+
21
+ # Keys the fixtures use for test scaffolding that are NOT wire keys.
22
+ FIXTURE_ONLY_CRITERIA_KEYS = {"criteria_name"} # resolved to a catalog PK by tests
23
+
24
+
25
+ def _fixtures():
26
+ files = sorted(FIXTURES.glob("*.json"))
27
+ assert len(files) == 9, f"expected the nine archetypes, found {len(files)}"
28
+ return [json.loads(f.read_text()) for f in files]
29
+
30
+
31
+ def _check_curve(curve, where):
32
+ from boarddata.types.comex import (
33
+ PayoutCurveEnvelope,
34
+ PayoutCurveOverlay,
35
+ PayoutCurvePayload,
36
+ PayoutCurvePoint,
37
+ )
38
+
39
+ unknown = set(curve) - set(PayoutCurvePayload.__annotations__)
40
+ assert not unknown, f"{where}: curve keys {sorted(unknown)}"
41
+ for i, pt in enumerate(curve.get("points", [])):
42
+ u = set(pt) - set(PayoutCurvePoint.__annotations__)
43
+ assert not u, f"{where} point[{i}]: {sorted(u)}"
44
+ for i, ov in enumerate(curve.get("overlays") or []):
45
+ u = set(ov) - set(PayoutCurveOverlay.__annotations__)
46
+ assert not u, f"{where} overlay[{i}]: {sorted(u)}"
47
+ for i, env in enumerate(curve.get("envelopes") or []):
48
+ u = set(env) - set(PayoutCurveEnvelope.__annotations__)
49
+ assert not u, f"{where} envelope[{i}]: {sorted(u)}"
50
+
51
+
52
+ def _check_disclosure(disc, where):
53
+ from boarddata.types.comex import DisclosureMap
54
+
55
+ unknown = set(disc) - set(DisclosureMap.__annotations__)
56
+ assert not unknown, f"{where}: disclosure keys {sorted(unknown)}"
57
+
58
+
59
+ def test_envelope_keys_map_to_signature_params():
60
+ from boarddata import BoardDataClient
61
+
62
+ params = set(inspect.signature(BoardDataClient.create_comex_compensation).parameters)
63
+ for fx in _fixtures():
64
+ unknown = set(fx.get("envelope", {})) - params
65
+ assert not unknown, f"{fx['isin']}: envelope keys {sorted(unknown)}"
66
+
67
+
68
+ def test_criteria_keys_are_typed():
69
+ from boarddata.types.comex import CriterionWritePayload
70
+
71
+ typed = set(CriterionWritePayload.__annotations__)
72
+ for fx in _fixtures():
73
+ for i, crit in enumerate(fx.get("criteria", [])):
74
+ unknown = set(crit) - typed - FIXTURE_ONLY_CRITERIA_KEYS
75
+ assert not unknown, f"{fx['isin']} criteria[{i}]: {sorted(unknown)}"
76
+ if crit.get("payout_curve"):
77
+ _check_curve(crit["payout_curve"], f"{fx['isin']} criteria[{i}]")
78
+ if crit.get("disclosure"):
79
+ _check_disclosure(crit["disclosure"], f"{fx['isin']} criteria[{i}]")
80
+
81
+
82
+ def test_rate_keys_are_typed():
83
+ from boarddata.types.comex import ComponentAchievementRateWritePayload
84
+
85
+ typed = set(ComponentAchievementRateWritePayload.__annotations__)
86
+ for fx in _fixtures():
87
+ for i, rate in enumerate(fx.get("component_achievement_rates", [])):
88
+ unknown = set(rate) - typed
89
+ assert not unknown, f"{fx['isin']} rate[{i}]: {sorted(unknown)}"
90
+ if rate.get("payout_curve"):
91
+ _check_curve(rate["payout_curve"], f"{fx['isin']} rate[{i}]")
92
+ if rate.get("disclosure"):
93
+ _check_disclosure(rate["disclosure"], f"{fx['isin']} rate[{i}]")
94
+
95
+
96
+ def test_field_source_keys_are_typed():
97
+ from boarddata.types.core import (
98
+ DerivationInput,
99
+ DerivationPayload,
100
+ FieldSourcePayload,
101
+ )
102
+
103
+ fs_typed = set(FieldSourcePayload.__annotations__)
104
+ d_typed = set(DerivationPayload.__annotations__)
105
+ i_typed = set(DerivationInput.__annotations__)
106
+ for fx in _fixtures():
107
+ for i, fs in enumerate(fx.get("field_sources", [])):
108
+ unknown = set(fs) - fs_typed
109
+ assert not unknown, f"{fx['isin']} field_sources[{i}]: {sorted(unknown)}"
110
+ der = fs.get("derivation")
111
+ if der:
112
+ u = set(der) - d_typed
113
+ assert not u, f"{fx['isin']} field_sources[{i}].derivation: {sorted(u)}"
114
+ for j, inp in enumerate(der.get("inputs", [])):
115
+ u = set(inp) - i_typed
116
+ assert not u, f"{fx['isin']} field_sources[{i}].derivation.inputs[{j}]: {sorted(u)}"