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.
- {boarddata-6.7.3 → boarddata-6.7.4}/PKG-INFO +1 -1
- {boarddata-6.7.3 → boarddata-6.7.4}/__init__.py +1 -1
- {boarddata-6.7.3 → boarddata-6.7.4}/_comex.py +66 -16
- {boarddata-6.7.3 → boarddata-6.7.4}/_committees.py +1 -3
- {boarddata-6.7.3 → boarddata-6.7.4}/_directors.py +4 -4
- {boarddata-6.7.3 → boarddata-6.7.4}/_utilities.py +3 -2
- {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/PKG-INFO +1 -1
- {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/SOURCES.txt +2 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/pyproject.toml +1 -1
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_comex.py +114 -4
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_directors.py +2 -2
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_exports.py +4 -4
- boarddata-6.7.4/tests/test_politic_fixture_conformance.py +116 -0
- boarddata-6.7.4/tests/test_types_politic.py +398 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/__init__.py +26 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/auditors.py +3 -3
- {boarddata-6.7.3 → boarddata-6.7.4}/types/comex.py +231 -10
- boarddata-6.7.4/types/core.py +172 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/directors.py +2 -2
- boarddata-6.7.3/types/core.py +0 -97
- {boarddata-6.7.3 → boarddata-6.7.4}/CLAUDE.md +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/README.md +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_assemblies.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_auditors.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_base.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_companies.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_criteria.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_documents.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_esg.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_indexes.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_persons.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_risks.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/_sentinel.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/dependency_links.txt +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/requires.txt +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/boarddata.egg-info/top_level.txt +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/cache.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/client.py +1 -1
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/__init__.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/adjudicate.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/apply.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/context.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/db.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/domains.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/models.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/oracle.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/registry.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/report.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/rules/__init__.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/rules/cross_company.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/rules/lexical.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/s3.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/dq/verify_s3.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/errors.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/py.typed +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/__init__.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_apply.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_batch.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_plan.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_revert.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_scan.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/scripts/dq_verify_s3.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/setup.cfg +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/__init__.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/conftest.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_assemblies.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_auditors.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_base.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_build_payload.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_cache.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_committees.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_companies.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_config.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_criteria.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_documents.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_esg.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_indexes.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_persons.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_risks.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_sentinel.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/tests/test_utilities.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/assemblies.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/committees.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/companies.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/criteria.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/documents.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/esg.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/indexes.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/persons.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/risks.py +0 -0
- {boarddata-6.7.3 → boarddata-6.7.4}/types/sentinel.py +0 -0
|
@@ -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
|
|
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 (``"
|
|
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 (``"
|
|
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:
|
|
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[
|
|
360
|
-
component_achievement_rates: list[
|
|
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 (``"
|
|
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
|
|
414
|
-
component_achievement_rates: List of achievement
|
|
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:
|
|
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[
|
|
525
|
-
component_achievement_rates: list[
|
|
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 (``"
|
|
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
|
|
579
|
-
component_achievement_rates: List of achievement
|
|
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
|
|
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 (``"
|
|
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 (``"
|
|
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 (``"
|
|
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 (``"
|
|
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
|
-
) ->
|
|
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
|
|
84
|
+
Dict mapping ``object_id -> field_name -> list`` of FieldSourceItem dicts.
|
|
84
85
|
|
|
85
86
|
Raises:
|
|
86
87
|
BoardDataError: On non-2xx API response.
|
|
@@ -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
|
|
@@ -27,7 +27,7 @@ class TestCreateComexCompensation:
|
|
|
27
27
|
c.create_comex_compensation(
|
|
28
28
|
"comex-uuid",
|
|
29
29
|
2024,
|
|
30
|
-
"
|
|
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
|
-
"
|
|
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"] == "
|
|
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, "
|
|
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, "
|
|
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"] == "
|
|
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="
|
|
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"] == "
|
|
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="
|
|
198
|
+
c.export_comex(nature="politic")
|
|
199
199
|
_, kwargs = mock_req.call_args
|
|
200
|
-
assert kwargs["params"]["nature"] == "
|
|
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)}"
|