Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/freshness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Freshness

on:
schedule:
- cron: "47 9 2 * *"
workflow_dispatch:

permissions:
contents: read

jobs:
check-demo-date:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"

# The checker imports only stdlib-backed constants, so no dependency install is needed.
- name: Check demo-data freshness
run: python scripts/check_freshness.py
12 changes: 6 additions & 6 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ their output contract contains aggregate dimensions and measures only.

## Trust controls

- Synthetic markers and reserved identifiers/domains are validated.
- The test harness blocks socket access.
- Do Not Contact removes an organization from outreach priority.
- Drafts and reports are local, synthetic, and human-reviewed.
- No page can obtain applicant-level output through a role or navigation path.
- Generated artifacts are reproducible but excluded from version control.
- Synthetic markers and reserved identifiers/domains are validated. <!-- control: synthetic-markers-validated -->
- The test harness blocks socket access. <!-- control: socket-guard -->
- Do Not Contact removes an organization from outreach priority. <!-- control: do-not-contact-override -->
- Drafts and reports are local, synthetic, and human-reviewed. <!-- control: drafts-human-reviewed -->
- No page can obtain applicant-level output through a role or navigation path. <!-- control: no-applicant-level-output -->
- Generated artifacts are reproducible but excluded from version control. <!-- control: generated-artifacts-untracked -->

## Production transition

Expand Down
81 changes: 81 additions & 0 deletions scripts/check_freshness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Scheduled freshness runbook.
Reads: DATA_AS_OF_DATE from src/tens_hq/constants.py.
Red: the synthetic demo date is more than 365 days old or the fact registry is empty.
Remediation: review the demo facts, regenerate them, and advance DATA_AS_OF_DATE.
Notification: GitHub emails the scheduled-run actor; 60 days of inactivity may auto-disable the schedule with warning.
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
from datetime import date, timedelta
from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))

from tens_hq.constants import DATA_AS_OF_DATE # noqa: E402


MAX_FACT_AGE_DAYS = 365


@dataclass(frozen=True)
class Fact:
name: str
as_of: date
remediation: str

@property
def deadline(self) -> date:
return self.as_of + timedelta(days=MAX_FACT_AGE_DAYS)


def fact_registry() -> tuple[Fact, ...]:
return (
Fact(
name="DATA_AS_OF_DATE",
as_of=DATA_AS_OF_DATE,
remediation="review the demo facts, regenerate them, and advance DATA_AS_OF_DATE",
),
)


def stale_facts(today: date) -> list[str]:
facts = fact_registry()
if not facts:
return ["fact registry is empty; restore at least one machine-readable dated fact"]
return [
f"{fact.name}: deadline {fact.deadline.isoformat()}; {fact.remediation}"
for fact in facts
if today > fact.deadline
]


def main() -> int:
parser = argparse.ArgumentParser(description="Check ROCC's dated demo facts for staleness.")
parser.add_argument("--today", type=date.fromisoformat, help="override today with YYYY-MM-DD")
args = parser.parse_args()
today = args.today or date.today()

facts = fact_registry()
findings = stale_facts(today)
if not facts:
return 1

for finding in findings:
print(f"STALE: {finding}")

for fact in facts:
status = "STALE" if today > fact.deadline else "FRESH"
print(
f"{status}: {fact.name} as of {fact.as_of.isoformat()} "
f"(deadline {fact.deadline.isoformat()})"
)
return 1 if findings else 0


if __name__ == "__main__":
raise SystemExit(main())
5 changes: 4 additions & 1 deletion src/tens_hq/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,10 @@ def render_site_readiness(data: DemoData, target: float, scenario: str) -> None:
cols[1].metric("90-day", f"{row90['projected_ratio']:.1%}", f"{row90['direction']:+.1%}")
cols[2].metric("180-day", f"{row180['projected_ratio']:.1%}")
cols[3].metric("Open roles", int(row90["open_roles_count"]))
cols[4].metric("Ready hires still needed", int(row90["qualified_hiring_need"] or 0))
cols[4].metric(
"Ready hires still needed",
int(0 if pd.isna(row90["qualified_hiring_need"]) else row90["qualified_hiring_need"]),
)
cols[5].metric("Projected pipeline arrivals", f"{row90['expected_ready_hires']:.1f}")
st.markdown(f'<div class="insight-box">{html.escape(row90["explanation"])}</div>', unsafe_allow_html=True)
_cov = row90["pipeline_coverage"]
Expand Down
2 changes: 1 addition & 1 deletion src/tens_hq/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def leadership_summary_markdown(forecasts: pd.DataFrame, queue: pd.DataFrame) ->
3, "qualified_hiring_need"
)
risks = "\n".join(
f"- {row.site_name}: {row.risk_status}, projected {row.projected_ratio:.1%}, need {int(row.qualified_hiring_need or 0)}"
f"- {row.site_name}: {row.risk_status}, projected {row.projected_ratio:.1%}, need {int(0 if pd.isna(row.qualified_hiring_need) else row.qualified_hiring_need)}"
for row in risk_sites.itertuples(index=False)
) or "- No At Risk or Critical sites in the selected scenario."
actions = "\n".join(
Expand Down
41 changes: 41 additions & 0 deletions tests/test_freshness_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

from datetime import date
from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))

import check_freshness # noqa: E402


def test_fresh_on_2026_07_27():
assert check_freshness.stale_facts(date(2026, 7, 27)) == []


def test_stale_after_365_days():
findings = check_freshness.stale_facts(date(2027, 7, 1))
assert len(findings) == 1
assert "DATA_AS_OF_DATE" in findings[0]
assert "deadline 2027-06-30" in findings[0]


def test_deadline_day_is_fresh():
assert check_freshness.stale_facts(date(2027, 6, 30)) == []


def test_stale_main_prints_remediation(monkeypatch, capsys):
monkeypatch.setattr(
sys, "argv", ["check_freshness.py", "--today", "2027-07-01"]
)

assert check_freshness.main() == 1
assert (
"review the demo facts, regenerate them, and advance DATA_AS_OF_DATE"
in capsys.readouterr().out
)


def test_registry_is_not_empty():
assert check_freshness.fact_registry()
143 changes: 143 additions & 0 deletions tests/test_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from __future__ import annotations

from dataclasses import replace
import inspect

import pytest

from tens_hq import pages
import tens_hq.services as services
from tens_hq.metrics import forecast_sites, source_performance
from tens_hq.services import (
build_outreach_queue,
leadership_summary_markdown,
outreach_email_draft,
site_report_markdown,
)


def test_do_not_contact_orgs_are_excluded_from_outreach_queue(demo_data):
do_not_contact = demo_data.organizations["relationship_status"].eq("Do Not Contact")
assert do_not_contact.sum() > 0

forecasts = forecast_sites(demo_data)
scores = source_performance(demo_data)
queue = build_outreach_queue(demo_data, scores, forecasts)

assert not queue["relationship_status"].eq("Do Not Contact").any()


def test_do_not_contact_partner_priority_score_is_zero(demo_data):
scored = source_performance(demo_data)
organization_id = scored.iloc[0]["organization_id"]
assert scored.iloc[0]["partner_priority_score"] != 0

organizations = demo_data.organizations.copy()
organizations.loc[
organizations["organization_id"].eq(organization_id), "relationship_status"
] = "Do Not Contact"

scores = source_performance(replace(demo_data, organizations=organizations))
do_not_contact = scores.loc[scores["organization_id"].eq(organization_id)]

assert len(do_not_contact) == 1
assert do_not_contact.iloc[0]["partner_priority_score"] == 0


def test_outreach_drafts_are_synthetic_and_carry_no_delivery_capability():
warm_subject, warm_body = outreach_email_draft(
"Synthetic Partner",
"Taylor Example",
"Synthetic Site",
"Administrative Support",
"Warm",
)
cold_subject, cold_body = outreach_email_draft(
"Synthetic Partner",
"Taylor Example",
"Synthetic Site",
"Administrative Support",
"Cold",
)

assert warm_subject
assert cold_subject
assert "ROCC Demo User (Synthetic)" in warm_body
assert "ROCC Demo User (Synthetic)" in cold_body
assert "has not been sent" in cold_body

source = inspect.getsource(services)
assert "smtplib" not in source
assert "requests" not in source
assert ".send(" not in source
assert "sendmail" not in source


def test_reports_carry_decision_boundary_banners(demo_data):
forecasts = forecast_sites(demo_data)
scores = source_performance(demo_data)
queue = build_outreach_queue(demo_data, scores, forecasts)

site_report = site_report_markdown(forecasts.iloc[0])
leadership_summary = leadership_summary_markdown(forecasts, queue)

assert "NOT AN OFFICIAL ODLH COMPLIANCE DETERMINATION" in site_report
assert "SYNTHETIC DEMO DATA — NOT FOR EMPLOYMENT OR COMPLIANCE DECISIONS" in leadership_summary


@pytest.mark.parametrize("missing_need", [None, float("nan")], ids=["none", "nan"])
def test_missing_qualified_hiring_need_is_zero_in_frame_consumers(
monkeypatch, demo_data, missing_need
):
forecasts90 = forecast_sites(demo_data)
needs = forecasts90["qualified_hiring_need"].astype(float).tolist()
needs[0] = missing_need
forecasts90 = forecasts90.assign(qualified_hiring_need=needs)
forecasts180 = forecast_sites(demo_data, horizon_days=180)

assert str(forecasts90["qualified_hiring_need"].dtype) == "float64"

scores = source_performance(demo_data)
queue = build_outreach_queue(demo_data, scores, forecasts90)
leadership_summary = leadership_summary_markdown(forecasts90, queue)

assert "North Harbor Services: At Risk" in leadership_summary
assert "need 0" in leadership_summary

rendered_metrics = {}

class MetricColumn:
def __enter__(self):
return self

def __exit__(self, *_args):
return False

def metric(self, label, value, *_args, **_kwargs):
rendered_metrics[label] = value

monkeypatch.setattr(
pages,
"_cached_forecast_sites",
lambda _data, _seed, _target, _scenario, horizon: (
forecasts90 if horizon == 90 else forecasts180
),
)
monkeypatch.setattr(pages, "_cached_source_performance", lambda *_args: scores)
monkeypatch.setattr(
pages.st,
"columns",
lambda spec, *_args, **_kwargs: [
MetricColumn() for _ in range(spec if isinstance(spec, int) else len(spec))
],
)
monkeypatch.setattr(pages.st, "selectbox", lambda _label, options: options[0])
monkeypatch.setattr(pages.st, "markdown", lambda *_args, **_kwargs: None)
monkeypatch.setattr(pages.st, "caption", lambda *_args, **_kwargs: None)
monkeypatch.setattr(pages.st, "plotly_chart", lambda *_args, **_kwargs: None)
monkeypatch.setattr(pages.st, "dataframe", lambda *_args, **_kwargs: None)
monkeypatch.setattr(pages.st, "info", lambda *_args, **_kwargs: None)

pages.render_site_readiness(demo_data, target=0.75, scenario="Base")

assert rendered_metrics["Ready hires still needed"] == 0
Loading
Loading