← Files ClaraARCHIVED FILE
modules/mix-contribution-analysis/vendor/modules/chart_harness/period_label_policy.py
10.3 KB · Oct 6, 2026 · 06:02 UTC
"""Mechanical policy for scenario labels and resolved period context."""
from __future__ import annotations
import re
from typing import Any, Mapping
from .period_contract import (
ACTUAL_LABELS,
BUDGET_LABELS,
COMPARISON_CURRENT_LABELS,
COMPARISON_PREVIOUS_LABELS,
FORECAST_LABELS,
PLAN_LABELS,
normalize_scenario_label,
)
__all__ = [
"has_resolved_period_context",
"is_scenario_label",
"looks_like_resolved_period_label",
"period_label_policy_texts",
"scenario_tokens_from_text",
"validate_period_label_policy",
]
_SCENARIO_LABELS = (
ACTUAL_LABELS
| BUDGET_LABELS
| COMPARISON_CURRENT_LABELS
| COMPARISON_PREVIOUS_LABELS
| FORECAST_LABELS
| PLAN_LABELS
)
_GENERIC_PERIOD_LABELS = {
"all",
"allperiods",
"baseline",
"comparison",
"current",
"currentperiod",
"period",
"periodone",
"periodzero",
"previous",
"prior",
}
_MONTH_PATTERN = (
r"jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|"
r"jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|"
r"dec(?:ember)?"
)
_PERIOD_PATTERNS = (
re.compile(r"\b\d{4}-\d{2}(?:-\d{2})?\b"),
re.compile(r"[’']\d{2}-\d{2}\b"),
re.compile(r"\bFY\d{2,4}\b", re.IGNORECASE),
re.compile(r"\b(?:\d{4}Q[1-4]|Q[1-4]\s*\d{4})\b", re.IGNORECASE),
re.compile(rf"\b(?:{_MONTH_PATTERN})[-\s_/]*\d{{2,4}}\b", re.IGNORECASE),
re.compile(rf"[~_](?:{_MONTH_PATTERN})[-\s_/]*\d{{2,4}}\b", re.IGNORECASE),
re.compile(r"\b(?:rolling|prior_year|previous)_\d+[mdw]_", re.IGNORECASE),
)
_TOKEN_PATTERN = re.compile(r"[A-Za-z0-9]+")
_PAIR_PATTERN = re.compile(
r"(?P<left>[A-Za-z0-9’'~_.-]+)\s+(?:to|vs|versus)\s+"
r"(?P<right>[A-Za-z0-9’'~_.-]+)",
re.IGNORECASE,
)
def is_scenario_label(value: Any) -> bool:
"""Return whether ``value`` is an exact scenario/comparison label."""
text = str(value or "").strip()
return bool(text) and normalize_scenario_label(text) in _SCENARIO_LABELS
def scenario_tokens_from_text(value: Any) -> list[str]:
"""Return exact scenario tokens present in display text."""
tokens: list[str] = []
for match in _TOKEN_PATTERN.findall(str(value or "")):
if is_scenario_label(match) and match not in tokens:
tokens.append(match)
return tokens
def looks_like_resolved_period_label(value: Any) -> bool:
"""Return whether text looks like a concrete period/window label."""
text = _clean_text(value)
if not text:
return False
normalized = normalize_scenario_label(text)
if normalized in _SCENARIO_LABELS or normalized in _GENERIC_PERIOD_LABELS:
return False
if any(pattern.search(text) for pattern in _PERIOD_PATTERNS):
return True
if re.search(r"\b\d{4}\b", text) and re.search(
r"\b(?:YTD|year to date|through|ending|calendar|fiscal)\b",
text,
flags=re.IGNORECASE,
):
return True
return False
def period_label_policy_texts(context: Mapping[str, Any]) -> list[str]:
"""Return display fields where period/scenario labels are expected."""
texts: list[str] = []
title_contract = context.get("title_contract")
if isinstance(title_contract, Mapping):
_append_text(texts, title_contract.get("when"))
title_lines = context.get("chart_title_lines")
if isinstance(title_lines, list) and title_lines:
_append_text(texts, title_lines[-1])
if not texts:
_append_text(texts, context.get("chart_title"))
_append_text(texts, context.get("title"))
comparison = context.get("comparison")
if isinstance(comparison, Mapping):
for key in (
"baseline_period",
"comparison_period",
"previous_period",
"current_period",
"baseline_label",
"comparison_label",
"previous_label",
"current_label",
):
_append_text(texts, comparison.get(key))
for value in _selected_period_values(context):
_append_text(texts, value)
return texts
def has_resolved_period_context(context: Mapping[str, Any]) -> bool:
"""Return whether context contains a concrete period/window reference."""
if _mapping_has_resolved_period(context.get("period_window")):
return True
options = context.get("options")
if isinstance(options, Mapping) and _mapping_has_resolved_period(
options.get("period_window")
):
return True
if _mapping_has_resolved_period(context.get("period_adapter")):
return True
if _mapping_has_resolved_period(context.get("comparison")):
return True
for value in _selected_period_values(context):
if looks_like_resolved_period_label(value):
return True
periods = context.get("periods")
if isinstance(periods, list) and any(
looks_like_resolved_period_label(value) for value in periods
):
return True
scenarios_by_period = context.get("scenarios_by_period")
if isinstance(scenarios_by_period, Mapping) and any(
looks_like_resolved_period_label(key) for key in scenarios_by_period
):
return True
return any(
looks_like_resolved_period_label(text)
for text in period_label_policy_texts(context)
)
def validate_period_label_policy(context: Mapping[str, Any]) -> dict[str, Any]:
"""Validate that scenario labels are backed by concrete period evidence."""
texts = period_label_policy_texts(context)
selected_periods = _selected_period_values(context)
display_texts = [text for text in texts if text not in selected_periods]
scenario_tokens = _dedupe(
token for text in texts for token in scenario_tokens_from_text(text)
)
resolved = has_resolved_period_context(context)
issues: list[dict[str, Any]] = []
same_period_line = _same_period_comparison_line(texts)
if same_period_line:
issues.append(
{
"code": "same_period_comparison_label",
"detail": "A period/scenario comparison uses the same label on both sides.",
"text": same_period_line,
}
)
if _has_bare_actual_label(display_texts, selected_periods) and not resolved:
issues.append(
{
"code": "bare_actual_without_period_context",
"detail": "A single AC/actual label is not a resolved period.",
}
)
if _scenario_only_selection(selected_periods) and not resolved:
issues.append(
{
"code": "scenario_periods_without_resolved_period_context",
"detail": "Selected periods contain only scenario labels.",
"selected_periods": selected_periods,
}
)
if scenario_tokens and not resolved:
issues.append(
{
"code": "scenario_labels_without_resolved_period_context",
"detail": "Scenario labels require a resolved period/window.",
"scenario_tokens": scenario_tokens,
}
)
status = "period_label_policy_not_applicable"
if issues:
status = "period_label_policy_failed"
elif scenario_tokens or selected_periods or resolved:
status = "period_label_policy_ok"
return {
"status": status,
"issues": issues,
"scenario_tokens": scenario_tokens,
"selected_periods": selected_periods,
"resolved_period_context": resolved,
"checked_texts": texts,
}
def _append_text(values: list[str], value: Any) -> None:
text = _clean_text(value)
if text and text not in values:
values.append(text)
def _clean_text(value: Any) -> str:
return re.sub(r"<[^>]+>", "", str(value or "")).strip()
def _selected_period_values(context: Mapping[str, Any]) -> list[str]:
values: list[str] = []
for source in (context, context.get("period_adapter")):
if not isinstance(source, Mapping):
continue
selected = source.get("selected_periods")
if not isinstance(selected, list):
continue
for value in selected:
text = _clean_text(value)
if text and text not in values:
values.append(text)
return values
def _mapping_has_resolved_period(value: Any) -> bool:
if not isinstance(value, Mapping):
return False
for key in (
"display_label",
"period_label",
"period",
"start_date",
"end_date",
"source_min_date",
"source_max_date",
"date",
):
if looks_like_resolved_period_label(value.get(key)):
return True
if key in {"start_date", "end_date", "source_min_date", "source_max_date"}:
date_value = _clean_text(value.get(key))
if re.fullmatch(r"\d{4}-\d{2}(?:-\d{2})?", date_value):
return True
for key in ("selected_periods", "available_periods", "input_periods"):
nested_values = value.get(key)
if isinstance(nested_values, list) and any(
looks_like_resolved_period_label(item) for item in nested_values
):
return True
for nested in value.values():
if isinstance(nested, Mapping) and _mapping_has_resolved_period(nested):
return True
return False
def _scenario_only_selection(selected_periods: list[str]) -> bool:
return bool(selected_periods) and all(
is_scenario_label(value) for value in selected_periods
)
def _has_bare_actual_label(texts: list[str], selected_periods: list[str]) -> bool:
if (
len(selected_periods) == 1
and normalize_scenario_label(selected_periods[0]) in ACTUAL_LABELS
):
return True
return any(normalize_scenario_label(text) in ACTUAL_LABELS for text in texts)
def _same_period_comparison_line(texts: list[str]) -> str | None:
for text in texts:
for match in _PAIR_PATTERN.finditer(text):
left = _comparison_side_label(match.group("left"))
right = _comparison_side_label(match.group("right"))
if left and right and left == right:
return text
return None
def _comparison_side_label(value: str) -> str:
return normalize_scenario_label(value.strip(" |,;:()[]{}"))
def _dedupe(values: Any) -> list[Any]:
result: list[Any] = []
for value in values:
if value not in result:
result.append(value)
return result
SHA-256: e03d299651e00918a803e39c135eaeeeda9d972b77336fc8032daf7a068d946b