From 7f8420ead1141b1af7e7d8a33ed2e51cc3f07dcb Mon Sep 17 00:00:00 2001 From: Ali Hesari Date: Sun, 20 Sep 2026 01:50:07 +0200 Subject: [PATCH] feat: read an account's per-network metric set accounts()->platformMetrics() answers in the platform's own vocabulary rather than the cross-network one: ad-break earnings, story taps, a retention curve, the search terms behind a listing. Needs the analytics scope, and a network whose access is still pending answers 503 platform_metrics_unavailable. --- README.md | 6 ++ src/Model/AccountPlatformMetrics.php | 30 +++++++++ src/Model/PlatformMetricRow.php | 36 +++++++++++ src/Model/PlatformMetricsBlock.php | 32 ++++++++++ src/Resource/AccountsResource.php | 18 ++++++ tests/PlatformMetricsTest.php | 95 ++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+) create mode 100644 src/Model/AccountPlatformMetrics.php create mode 100644 src/Model/PlatformMetricRow.php create mode 100644 src/Model/PlatformMetricsBlock.php create mode 100644 tests/PlatformMetricsTest.php diff --git a/README.md b/README.md index be8c0e9..5e44fe0 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,12 @@ $account = $client->accounts()->get('acc_1'); $health = $client->accounts()->health('acc_1'); $client->accounts()->disconnect('acc_1'); +// The numbers only this account's network reports, in its own vocabulary. +$metrics = $client->accounts()->platformMetrics('acc_1'); +foreach ($metrics->account->metrics as $row) { + echo "{$row->label}: {$row->value}\n"; +} + // Rename; null restores the platform name. $client->accounts()->update('acc_1', 'Brand HQ'); $client->accounts()->move('acc_1', $otherWorkspaceId); diff --git a/src/Model/AccountPlatformMetrics.php b/src/Model/AccountPlatformMetrics.php new file mode 100644 index 0000000..f48cd13 --- /dev/null +++ b/src/Model/AccountPlatformMetrics.php @@ -0,0 +1,30 @@ + */ + public readonly array $metrics, + ) { + parent::__construct($raw); + } + + public static function fromArray(mixed $data): static + { + $data = is_array($data) ? $data : []; + + return new self( + $data, + self::str($data, 'fetched_at'), + self::str($data, 'external_post_id'), + PlatformMetricRow::listFrom(self::seq($data, 'metrics')), + ); + } +} diff --git a/src/Resource/AccountsResource.php b/src/Resource/AccountsResource.php index 71bcdf4..b9edef0 100644 --- a/src/Resource/AccountsResource.php +++ b/src/Resource/AccountsResource.php @@ -5,6 +5,7 @@ namespace Fopost\Sdk\Resource; use Fopost\Sdk\Model\AccountMove; +use Fopost\Sdk\Model\AccountPlatformMetrics; use Fopost\Sdk\Model\AccountRename; use Fopost\Sdk\Model\DiscordChannel; use Fopost\Sdk\Model\DiscordIdentity; @@ -85,6 +86,23 @@ public function health(string $accountId): array return self::asArray(self::unwrap($this->http->get("/accounts/{$accountId}/health"))); } + /** + * The numbers only this account's network reports, in its own vocabulary. + * + * Ad-break earnings, story taps, a retention curve, the search terms behind a + * listing — keyed by the platform's own metric names, read from the newest + * collected snapshot rather than fetched live. Needs the `analytics` scope. + * + * A network whose metric access has not been granted yet answers 503 + * (`platform_metrics_unavailable`) rather than an empty set. + */ + public function platformMetrics(string $accountId): AccountPlatformMetrics + { + return AccountPlatformMetrics::fromArray( + self::unwrap($this->http->get("/accounts/{$accountId}/insights", ['raw' => 'true'])), + ); + } + /** Rename the account; null or an empty string restores the platform name. */ public function update(string $accountId, ?string $displayName): AccountRename { diff --git a/tests/PlatformMetricsTest.php b/tests/PlatformMetricsTest.php new file mode 100644 index 0000000..c38aef2 --- /dev/null +++ b/tests/PlatformMetricsTest.php @@ -0,0 +1,95 @@ +transport->push(200, ['data' => [ + 'platform' => 'facebook', + 'account' => [ + 'fetched_at' => '2026-09-20T02:00:00.000Z', + 'metrics' => [ + [ + 'key' => 'page_daily_video_ad_break_earnings', + 'label' => 'Ad Break Earnings', + 'kind' => 'currency_usd', + 'value' => 42.15, + ], + [ + 'key' => 'page_impressions_paid', + 'label' => 'Paid Impressions', + 'kind' => 'count', + 'value' => 1500, + ], + ], + ], + 'post' => [ + 'external_post_id' => '123_456', + 'fetched_at' => '2026-09-20T02:00:00.000Z', + 'metrics' => [], + ], + ]]); + + $metrics = $this->client()->accounts()->platformMetrics('a_1'); + + $this->assertSame( + 'https://api.fopost.com/v1/accounts/a_1/insights?raw=true', + $this->transport->last()['url'], + ); + $this->assertSame('facebook', $metrics->platform); + $this->assertSame('2026-09-20T02:00:00.000Z', $metrics->account->fetchedAt); + $this->assertCount(2, $metrics->account->metrics); + $this->assertSame('page_daily_video_ad_break_earnings', $metrics->account->metrics[0]->key); + $this->assertSame('currency_usd', $metrics->account->metrics[0]->kind); + $this->assertSame(42.15, $metrics->account->metrics[0]->value); + $this->assertSame('123_456', $metrics->post->externalPostId); + $this->assertSame([], $metrics->post->metrics); + } + + public function testASeriesValueSurvivesAsAList(): void + { + $this->transport->push(200, ['data' => [ + 'platform' => 'youtube', + 'account' => [ + 'fetched_at' => null, + 'metrics' => [[ + 'key' => 'daily_views', + 'label' => 'Views by Day', + 'kind' => 'series', + 'value' => [['day' => '2026-09-19', 'views' => 600]], + ]], + ], + 'post' => ['external_post_id' => null, 'fetched_at' => null, 'metrics' => []], + ]]); + + $metrics = $this->client()->accounts()->platformMetrics('a_1'); + + $this->assertSame( + [['day' => '2026-09-19', 'views' => 600]], + $metrics->account->metrics[0]->value, + ); + $this->assertNull($metrics->account->fetchedAt); + } + + public function testAPendingMetricGrantThrows(): void + { + $this->transport->push(503, [ + 'error' => 'platform_metrics_unavailable', + 'message' => 'google-business metrics are not available on this deployment yet.', + ]); + + try { + $this->client(1)->accounts()->platformMetrics('a_1'); + $this->fail('Expected an ApiException'); + } catch (ApiException $e) { + $this->assertSame(503, $e->status); + $this->assertSame('platform_metrics_unavailable', $e->errorCode); + } + } +}