Skip to content

Wire homepage investor count to the live API - #82

Open
stenehrlich-tuleva wants to merge 2 commits into
masterfrom
wire-homepage-investor-count
Open

Wire homepage investor count to the live API#82
stenehrlich-tuleva wants to merge 2 commits into
masterfrom
wire-homepage-investor-count

Conversation

@stenehrlich-tuleva

Copy link
Copy Markdown
Contributor

Why

The "N Eesti inimest kogub Tuleva indeksfondides" figure on the front page is typed in by hand in wp-admin. It was last changed between 12 April and 11 May 2026 and has been frozen since — the site shows 85 224 against an actual 86 233, about 1 000 people short.

onboarding-service #1643 shipped GET /v1/statistics/investor-count on 2026-06-04 specifically to automate this ("Automates the ... figure on the tuleva.ee front page, which is currently edited by hand in wp-admin"). The endpoint works, but nothing ever consumed it — an org-wide code search finds zero callers. This is the missing half.

What changed

  • helpers/extras.php — replaces the dead get_member_count() with get_investor_count(), which reads /v1/statistics/investor-count and caches it in a transient.
    • The old helper was referenced nowhere, pointed at /v1/members (ühistu members, 9 729 — the wrong metric), and called stream_context_set_default(), mutating the default stream context for the whole request.
  • components/credentials.php and components/team-hero.php — one line each:
    $members_count = get_investor_count() ?: get_field('members_count', 'option');

The members_count ACF field stays as the fallback. The existing if ($members_count && $members_count_description) guard already hides the block on a falsy value, so the worst case is today's behaviour, never a blank or a zero.

Caching / load

mv_kpi_new only moves once a month, so the response is cached for a day: the displayed number changes about monthly and is never more than a day stale.

Render-time safety, since this is the highest-traffic page:

  • 1s timeout, same guard the fund pages already use (fund-stocks-content.php:20-29). Verified bounded: an unresponsive host returns after 1010ms.
  • A failed call is cached for 5 minutes, so an outage costs one slow render per five minutes, not one per visitor.
  • No DB load added — the endpoint is Cache-Control: public, max-age=1h server-side.

Test plan

Ran the real get_investor_count() source against the live endpoint with stubbed transient functions:

  • Happy path returns 86 233 in 213ms, renders as 86 233, cached for a day
  • Second call served from cache, no network round trip
  • Unreachable API returns 0, fails fast, failure cached 5 min (not a day)
  • 0 ?: $acf falls through to the ACF value; a real count wins over it
  • php -l clean on all three files
  • After deploy: curl -s https://tuleva.ee/ | grep -A2 membercount tracks curl -s https://onboarding-service.tuleva.ee/v1/statistics/investor-count
  • Check the team page too — it uses the same field

Note

Worth bumping the ACF field to the current value in wp-admin regardless, since it stays as the fallback.

🤖 Generated with Claude Code

stenehrlich-tuleva and others added 2 commits August 4, 2026 14:27
The "N Eesti inimest kogub Tuleva indeksfondides" figure on the front page was
typed in by hand in wp-admin, and had not been touched since early May 2026 —
85 224 displayed against an actual 86 233.

onboarding-service PR #1643 shipped GET /v1/statistics/investor-count in June
specifically to automate this, but nothing ever consumed it. This is the missing
half: credentials.php and team-hero.php now read the live count, falling back to
the members_count ACF field if the call fails, so the block can never render
blank or zero.

Replaces the dead get_member_count() helper, which was referenced nowhere and
pointed at /v1/members (ühistu members, 9 729 — the wrong metric). It also called
stream_context_set_default(), which mutated the default stream context for the
whole request.

The API response is cached for a day. mv_kpi_new only moves once a month, so the
displayed number changes about monthly while staying at most a day behind. A
failed call is cached for five minutes and the 1s timeout is bounded (verified:
an unresponsive host returns after 1010ms), so an outage costs one slow render
per five minutes rather than one per visitor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ate value

Follow-up to the review pass on this branch. Four fixes:

1. team-hero.php no longer calls the API. It assigns $members_count but never
   renders it (the assignment was already dead code on master), so wiring it up
   turned a free get_field() into a blocking HTTP call for a number that is never
   displayed. Reverted to match master exactly.

2. A failed refresh no longer falls back to the hand-entered ACF field. The last
   count the API returned successfully is kept in an option, so an outage keeps
   showing yesterday's real number instead of dropping to members_count, which
   can be months stale. Previously, one failed refresh after a day of correct
   values would have put 85 224 back on the front page.

3. Added the localhost guard the four fund templates already use, so local dev
   stops calling the production API and polluting its observability.

4. Value validation: require is_numeric, so a malformed {"count":"70000abc"} is
   rejected rather than cast to 70000, and mirror the endpoint's own SQL sanity
   bounds with an upper limit of 500000 alongside the existing floor.

Verified with a harness that runs the real function source against stubbed
WordPress functions, with get_transient modelling the options-table backend
returning scalars as strings: 21/21 checks pass, covering the live endpoint,
cache hit, failure-keeps-last-good, cold-start-falls-through-to-ACF, the
localhost guard, and eight value-validation cases.

Cache stampede on transient expiry is a known remaining gap, reduced but not
closed by the last-good fallback. Left for a follow-up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@stenehrlich-tuleva

Copy link
Copy Markdown
Contributor Author

Second round: review fixes (571929b)

Ran two independent adversarial reviews over the first commit. Four findings fixed:

  1. team-hero.php reverted to match master. It assigns $members_count but never renders it — the assignment was already dead code before this PR, so wiring it to the API turned a free get_field() into a blocking HTTP call for a number that is never displayed. It no longer calls the API at all. (This supersedes the "check the team page too" line in the original test plan above — that page does not show the count.)

  2. A failed refresh now keeps the last good count. Previously, one failed refresh after a day of correct values would have dropped straight to the hand-entered ACF field and put 85 224 back on the front page. The last successful count is now kept in an option, so an outage keeps showing yesterday's real number. The ACF field is still the final fallback for a cold start where the API has never answered.

  3. Localhost guard added, matching the four fund templates, so local dev stops calling the production API.

  4. Value validation tightened: is_numeric so a malformed {"count":"70000abc"} is rejected rather than silently cast to 70000, plus an upper bound of 500000 mirroring the endpoint's own SQL sanity check.

Verification

Harness runs the real function source against stubbed WordPress functions, with get_transient modelling the options-table backend returning scalars as strings (the more adversarial of the two backends). 21/21 checks pass: live endpoint, cache hit, failure-keeps-last-good, cold-start-falls-through-to-ACF, localhost guard, and 8 value-validation cases. CircleCI green.

Known remaining gap

Cache stampede on transient expiry. There is no lock, so when the day-long transient expires, concurrent requests each make their own API call. Reduced but not closed by the last-good fallback. Worth a follow-up if front-page concurrency justifies it — the call is bounded at 1s and the endpoint is max-age=1h, so the blast radius is limited.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant