diff --git a/composer.lock b/composer.lock index 8585326..4dadefc 100644 --- a/composer.lock +++ b/composer.lock @@ -3120,24 +3120,24 @@ }, { "name": "masterminds/html5", - "version": "2.10.1", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fd5018f6815fff903946d0564977b44ce8010e29" + "reference": "a1e7a2f88ee13635d86fc61cfbdf2306a76ddfc7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fd5018f6815fff903946d0564977b44ce8010e29", - "reference": "fd5018f6815fff903946d0564977b44ce8010e29", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/a1e7a2f88ee13635d86fc61cfbdf2306a76ddfc7", + "reference": "a1e7a2f88ee13635d86fc61cfbdf2306a76ddfc7", "shasum": "" }, "require": { "ext-dom": "*", - "php": ">=5.3.0" + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9 || ^10" + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10" }, "type": "library", "extra": { @@ -3181,9 +3181,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.1" + "source": "https://github.com/Masterminds/html5-php/tree/2.11.0" }, - "time": "2026-06-23T18:43:15+00:00" + "time": "2026-08-18T06:18:41+00:00" }, { "name": "monolog/monolog", @@ -12001,16 +12001,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.95.18", + "version": "v3.95.20", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "a8b4e4216faabf67f4e96110ee99a48c96e4e683" + "reference": "4d6c9886a77dbc1db3b9eaca6472cedf9eb1fab7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8b4e4216faabf67f4e96110ee99a48c96e4e683", - "reference": "a8b4e4216faabf67f4e96110ee99a48c96e4e683", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4d6c9886a77dbc1db3b9eaca6472cedf9eb1fab7", + "reference": "4d6c9886a77dbc1db3b9eaca6472cedf9eb1fab7", "shasum": "" }, "require": { @@ -12046,7 +12046,6 @@ "infection/infection": "^0.32.7", "justinrainbow/json-schema": "^6.10.0", "keradus/cli-executor": "^2.3", - "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", @@ -12094,7 +12093,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.18" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.20" }, "funding": [ { @@ -12102,7 +12101,7 @@ "type": "github" } ], - "time": "2026-07-30T15:46:02+00:00" + "time": "2026-08-19T16:28:14+00:00" }, { "name": "myclabs/deep-copy", @@ -13496,16 +13495,16 @@ }, { "name": "rector/rector", - "version": "2.6.2", + "version": "2.6.3", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "03cd615cdd5648abb5f10ff3a684fdb976687190" + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/03cd615cdd5648abb5f10ff3a684fdb976687190", - "reference": "03cd615cdd5648abb5f10ff3a684fdb976687190", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", + "reference": "7e46709996a4b3dc59e1d6ecbb6a38ace335bd58", "shasum": "" }, "require": { @@ -13544,7 +13543,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.6.2" + "source": "https://github.com/rectorphp/rector/tree/2.6.3" }, "funding": [ { @@ -13552,7 +13551,7 @@ "type": "github" } ], - "time": "2026-08-12T06:23:05+00:00" + "time": "2026-08-18T22:01:18+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/packages/zenstruck_schedule.yaml b/config/packages/zenstruck_schedule.yaml index f0a8e26..2cfa15a 100644 --- a/config/packages/zenstruck_schedule.yaml +++ b/config/packages/zenstruck_schedule.yaml @@ -59,14 +59,10 @@ when@prod: frequency: "0 20-23,0-7 * * *" description: "Syncs BusPro XML data hourly outside peak hours" - - task: app:bpn:sync-contingents --horizon-months=3 - frequency: "*/15 8-19 * * *" - description: "Syncs near-term contingent snapshots every 15 min. during peak hours" + - task: app:bpn:sync-contingents + frequency: "5-59/15 8-19 * * *" + description: "Syncs the full contingent horizon every 15 min. during peak hours" - - task: app:bpn:sync-contingents --horizon-months=3 + - task: app:bpn:sync-contingents frequency: "5 20-23,0-7 * * *" - description: "Syncs near-term contingent snapshots hourly outside peak hours" - - - task: app:bpn:sync-contingents --horizon-months=24 - frequency: "40 2,13 * * *" - description: "Syncs the full contingent horizon twice daily" + description: "Syncs the full contingent horizon hourly outside peak hours" diff --git a/src/Command/BpnSyncContingentsCommand.php b/src/Command/BpnSyncContingentsCommand.php index 24a5b76..f8733c5 100644 --- a/src/Command/BpnSyncContingentsCommand.php +++ b/src/Command/BpnSyncContingentsCommand.php @@ -26,7 +26,9 @@ use Symfony\Component\Console\Style\SymfonyStyle; /** * Console entrypoint for the scheduled contingent snapshot sync. * - * Runs at two cadences: a short horizon frequently, the full horizon a couple of times a day. + * Scheduled over the full horizon throughout: every 15 minutes during peak hours, hourly + * outside them. The options exist for ad-hoc runs — a narrower --horizon-months, or --hotel + * to re-sync a single accommodation while debugging. */ class BpnSyncContingentsCommand extends Command { @@ -48,7 +50,7 @@ class BpnSyncContingentsCommand extends Command protected function configure(): void { $this - ->addOption('horizon-months', null, InputOption::VALUE_REQUIRED, 'How many months ahead to sync', '24') + ->addOption('horizon-months', null, InputOption::VALUE_REQUIRED, 'How many months ahead to sync (defaults to the full horizon)', '24') ->addOption('hotel', null, InputOption::VALUE_REQUIRED, 'Restrict the run to a single calendarCode') ->addOption('stale-after', null, InputOption::VALUE_REQUIRED, 'Minimum minutes since the last sync before an accommodation is eligible (ignored with --force)', '0') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Sync even if the snapshot was refreshed recently') @@ -148,10 +150,10 @@ class BpnSyncContingentsCommand extends Command // Only a *total* failure is worth failing the task over: that means upstream is down or // the API key is rejected, and the scheduler's failure mail is genuinely actionable. - // A single hotel breaking must stay quiet — this task runs every 15 minutes, so failing - // on it would mail dozens of times a day until someone silenced the task. That hotel is - // not lost track of: its own calendar serves a 502, and contingent_sync_state records - // failure_count and last_error. + // A single hotel breaking must stay quiet — this task runs every 15 minutes during the + // day, so failing on it would mail dozens of times a day until someone silenced the task. + // That hotel is not lost track of: its own calendar serves a 502, and + // contingent_sync_state records failure_count and last_error. return $processed > 0 && $failed === $processed ? Command::FAILURE : Command::SUCCESS; } diff --git a/src/Entity/Groups/ContingentSyncState.php b/src/Entity/Groups/ContingentSyncState.php index 8908806..5a34f00 100644 --- a/src/Entity/Groups/ContingentSyncState.php +++ b/src/Entity/Groups/ContingentSyncState.php @@ -159,8 +159,9 @@ class ContingentSyncState /** * Records a successful sync of a window ending at $horizonTo. * - * The horizon only ever grows: the near-term job runs far more often than the full-horizon - * one, and must not discard the reach the latter established. + * The horizon only ever grows, so an ad-hoc run over a narrow window — say + * `--horizon-months=1` while debugging — cannot discard the reach the scheduled full sync + * established. */ public function recordSuccess(\DateTimeImmutable $syncedAt, \DateTimeImmutable $horizonTo): self { diff --git a/src/Service/ContingentSnapshotManager.php b/src/Service/ContingentSnapshotManager.php index 7036826..995495d 100644 --- a/src/Service/ContingentSnapshotManager.php +++ b/src/Service/ContingentSnapshotManager.php @@ -37,8 +37,8 @@ class ContingentSnapshotManager * Refreshes [dateFrom, dateTo] for a single accommodation. * * Days are fetched, diffed against what is stored and then persisted; the snapshot outside - * the requested window is left untouched, which is what lets the near-term and full-horizon - * schedules run at different cadences without fighting each other. + * the requested window is left untouched, so a narrow ad-hoc run cannot truncate what a + * full-horizon run already stored. */ public function sync( Accommodation $accommodation, diff --git a/src/Service/ContingentSnapshotReader.php b/src/Service/ContingentSnapshotReader.php index 1a0dd89..016c177 100644 --- a/src/Service/ContingentSnapshotReader.php +++ b/src/Service/ContingentSnapshotReader.php @@ -24,8 +24,9 @@ class ContingentSnapshotReader /** * How old the snapshot may get before we stop presenting it as current availability. * - * The near-term sync runs every 15 minutes during the day and hourly overnight, so this only - * trips when the scheduler is genuinely broken, never on the normal cadence. + * The sync runs every 15 minutes during the day and hourly overnight, so even the sparsest + * cadence leaves five missed runs of slack: this only trips when the scheduler is genuinely + * broken, never on the normal cadence. */ private const string MAX_SNAPSHOT_AGE = '-6 hours';