Skip to content

Three-model AI pipeline: classify, rewrite and roundups on Foundry with Flex (#581) - #599

Open
rvanmaanen wants to merge 26 commits into
mainfrom
feature/581-three-model-pipeline
Open

rvanmaanen wants to merge 26 commits into
mainfrom
feature/581-three-model-pipeline

Conversation

@rvanmaanen

@rvanmaanen rvanmaanen commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #581

Why

Every article and every weekly roundup went through one gpt-5.2 call per item or per step. That cost about €23.46 last month and gave inconsistent categorization, rewrites that sometimes dropped sections, and roundups that needed several extra AI passes. This PR splits the work over three models on Azure AI Foundry (Sweden Central, GlobalStandard) and runs background work on the Flex tier (50% cheaper). Estimated cost from measured tokens: about €15/month on Flex (about €30 if everything fell back to Standard).

What changes

Step Profile Model What it does
1. Classify Classification gpt-6.1-sol (low) Strict JSON schema: include/exclude, sections, tags, excerpt, significance, products, per-section roles, key facts and dated timeline events
2. Rewrite Rewrite gpt-6.1-sol (low) Clean markdown, only for included items. Output limit scales with article length (8k to 60k reasoning plus output tokens, a safety limit instead of a fixed small cap), so long articles are no longer cut
3a. Roundup writer RoundupNewsWriter gpt-6-astra (medium) One writer call per section per week, replacing the writer, enhancer, condenser and relevance filter
3b. Roundup metadata RoundupMetadata gpt-6.1-sol (low) Title, tags and introduction
Timeline backfill TimelineExtraction gpt-6-luna (low) Extracts only the dated timeline events from already stored articles. It has its own profile because it needs no include/exclude strictness and costs about 20x less than sol
  • AI client: v1 API with per-step profiles (AiCategorization:Profiles:*), patient Flex retries with fallback to Standard after 30 minutes, and per-call usage logging. Interactive calls (ad-hoc add, apply-transcript) use Standard
  • Timeline events: new timeline_events table (migration 049), filled during processing. A manual, resumable backfill job (POST /api/admin/timeline-backfill/trigger, dashboard button) fills it for existing articles
  • Roundups: lead stories get full paragraphs, other items a one-liner. Code builds the "Other X News" completeness block, "Also relevant this week" and "Dates to know" from the timeline events
  • Infra: openai.bicep deploys the three models next to gpt-5.2 (kept as rollback). applications.bicep sets the per-profile app settings
  • Prompts: classify prompt gets significance and product rules and excludes event announcements and promotional tip lists. The rewrite prompt leaves out related-post lists. The timeline prompt explains what a milestone is instead of capping the number of events

Evidence

An evaluation harness (tests/TechHub.Infrastructure.Tests/Evaluation, skipped unless TECHHUB_EVAL=1) ran the real models against a restore of production:

  • Classification of 100 included articles: 92-93% stay included. Of 73 articles production skipped, gpt-6.1-sol and gpt-5.2 keep 95.9% excluded, gpt-6-luna only 89.0% and lost on medium reasoning (82.2%), which is why classification uses sol
  • Rewrites: owner review found them better structured and better written. A mechanical fact check of all numbers, URLs and code terms against the original articles found nothing invented or altered
  • Roundups (7 sections, one week): 0 invalid links, 0 missing lead stories and 0 numbers missing from the sources. Production had 1-3 missing leads in four sections, one link matching no article and a few unsourced numbers
  • Timeline: bundled release notes no longer produce an event per bullet

Go-live and rollback

Merging and deploying switches production to the new models. After the deploy:

  1. Run the timeline backfill once. It uses the TimelineExtraction profile (gpt-6-luna), so for all ~9,000 articles it costs about €1.4 on Flex (about €2.8 on Standard), a one-off
  2. Watch cost, Flex versus fallback share and processed_urls failures for a month

The appsettings.json defaults are the new models and Flex as well, so nothing in the application still points at gpt-5.2; the gpt-5.2 deployment stays in Azure only as a rollback target.

Rollback: set a profile's deploymentName back to gpt-5.2 with service tier default in infra/applications.bicep and redeploy the application phase.

Review notes

  • The evaluation stages are skipped without TECHHUB_EVAL=1 (6 skipped tests). They cost money and need az login
  • No E2E test covers the new dashboard "Backfill Timeline" button (no existing dashboard E2E)
  • The E2E phase 1 database performance tests exceed their thresholds against a local database restored from production (about 9,000 items). They are unrelated to this change and I did not run the Playwright E2E on the final code
  • The gpt-6-luna deployment is kept as a spare and unused
  • Roundup article selection uses created_at, as before

rvanmaanen and others added 15 commits October 21, 2026 19:50
… gpt-5.2 (#581)

Replace the single model deployment in openai.bicep with a modelDeployments array,
deployed one at a time, each using the content-categorization RAI policy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AiCategorization:Profiles (Classification, Rewrite, RoundupNewsWriter, RoundupMetadata)
replace the single DeploymentName/ApiVersion. Requests are built by AiRequestBodyBuilder
(model, reasoning_effort, service_tier; no temperature) and sent to /openai/v1/chat/completions.
All profiles still point at gpt-5.2 on the default tier, so behaviour is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…age logging (#581)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eps (#581)

- content-classify-system.md / content-rewrite-system.md replace system-message.md
- ClassifyAsync returns include/exclude, sections, roundup metadata (schema v2) and timeline events
- CategorizeAsync rewrites only included items; a rewrite failure fails the item
- ai_metadata schema v2; roundup backfill classifies only and treats v1 as missing
- interactive (ad-hoc, apply-transcript) calls use the Standard tier

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- migration 049: content_timeline_events and content_timeline_extractions
- TimelineEventRepository replaces an article's events in one transaction
- events are written for processed items and for apply-transcript; apply-transcript now writes ai_metadata v2

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lt blocks (#581)

- split articles per section into lead (full write-up) and brief (one-line mention) from section_roles
- one writer call (key facts + last week's headings) and one strict-schema metadata call per section
- code builds the completeness check, 'Also relevant this week' and 'Dates to know' blocks and the table of contents
- remove narrative enhancer, condenser, relevance filter, GetArticlesForWeekAsync and CondensingEnabled
- fix previous-roundup lookup that skipped last week's roundup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ExtractTimelineEventsAsync extracts only timeline events from stored articles (strict schema)
- TimelineBackfillService scans unscanned articles (force / published-after / collection / limit)
- TimelineBackfillBackgroundService (manual trigger only), POST /api/admin/timeline-backfill/trigger, cancel support
- admin dashboard button and job type label

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…581)

- api.bicep takes aiProfiles and writes AiCategorization__Profiles__<Step>__* app settings; all steps stay on gpt-5.2 until go-live
- Setup-UserSecrets reads the per-profile settings
- document the two-step processing, Flex handling, roundup pipeline, timeline events and backfill job

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ndup backfill (#581)

- ModelEvaluationTests: classification, rewrite, timeline and roundup stages against a restored production database (skipped unless TECHHUB_EVAL=1)
- the roundup metadata backfill stores the timeline events its classification already extracted
- docs: how to run and read the evaluation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…581)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…table) (#581)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… exclude event and promo posts; skipped-URL control (#581)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…udes) (#581)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 19:57
@rvanmaanen
rvanmaanen requested a review from rajbos as a code owner October 21, 2026 19:57
Comment thread src/TechHub.Api/Endpoints/AdminEndpoints.cs Fixed
Comment thread src/TechHub.Api/Endpoints/AdminEndpoints.cs Fixed
Comment thread src/TechHub.Api/Endpoints/AdminEndpoints.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved timeline integrity and extraction issues, plus roundup link correctness gaps, need fixes before approval.

5 open issues
What changed in this PR

Splits TechHub’s AI processing into configurable classification, rewrite and roundup profiles, with Flex retries to reduce background-processing costs.

Changes:

  • Adds profile-based AI requests, usage logging and deployment configuration.
  • Stores timeline events and introduces a resumable admin backfill.
  • Simplifies roundup generation and expands supporting tests and documentation.
File Description
tests/​TechHub.Web.Tests/​Services/​TechHubApiClientAdminTests.cs Tests backfill client requests.
tests/​TechHub.Infrastructure.Tests/​Services/​TimelineEventParserTests.cs Tests event parsing and dates.
tests/​TechHub.Infrastructure.Tests/​Services/​RoundupRelevanceFilterTests.cs Removes obsolete ranking tests.
tests/​TechHub.Infrastructure.Tests/​Services/​RoundupBlocksBuilderTests.cs Tests roundup blocks and ordering.
tests/​TechHub.Infrastructure.Tests/​Services/​ContentProcessingServiceTests.cs Tests timeline persistence during processing.
tests/​TechHub.Infrastructure.Tests/​Services/​AiRequestBodyBuilderTests.cs Tests profile-based request bodies.
tests/​TechHub.Infrastructure.Tests/​Services/​AiCompletionClientTests.cs Updates expected API endpoint.
tests/​TechHub.Infrastructure.Tests/​Services/​AiCompletionClientFlexTests.cs Tests Flex retries, fallback and logging.
tests/​TechHub.Infrastructure.Tests/​Repositories/​TimelineEventRepositoryTests.cs Tests event storage and replacement.
tests/​TechHub.Infrastructure.Tests/​AGENTS.md Documents opt-in model evaluation.
tests/​TechHub.Api.Tests/​TechHubStubbedExternalServicesApiFactory.cs Extends AI stubs with timeline data.
tests/​TechHub.Api.Tests/​Services/​ContentProcessingBackgroundServiceTests.cs Supplies the timeline dependency.
tests/​TechHub.Api.Tests/​Endpoints/​AdminTimelineBackfillEndpointTests.cs Tests backfill validation and execution.
tests/​TechHub.Api.Tests/​Endpoints/​AdminContentItemsEndpointsTests.cs Checks transcript metadata and events.
tests/​powershell/​Setup-UserSecrets.Tests.ps1 Tests AI profile secret configuration.
src/​TechHub.Web/​wwwroot/​css/​admin.css Styles timeline job badges.
src/​TechHub.Web/​Services/​TechHubApiClient.cs Adds backfill trigger requests.
src/​TechHub.Web/​Services/​ITechHubApiClient.cs Exposes the backfill client contract.
src/​TechHub.Web/​Components/​Pages/​Admin/​AdminDashboard.razor Adds backfill controls and job display.
src/​TechHub.Infrastructure/​TechHub.Infrastructure.csproj Updates embedded prompt resources.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupRelevanceFilter.cs Removes the relevance filter.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupNewsWriter.cs Writes sections from lead-article facts.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupNarrativeEnhancer.cs Removes the separate enhancement pass.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupMetadataGenerator.cs Uses a profile and strict schema.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupHeadings.cs Extracts previous-roundup headings.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupGeneratorService.cs Coordinates simplified generation and backfill.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupGenerationExtensions.cs Updates service registrations.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupCondenser.cs Removes the condensing pass.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupBlocksBuilder.cs Builds completeness, brief and date blocks.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupArticleSelector.cs Splits and orders leads and briefs.
src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupAiHelper.cs Applies per-step AI profiles.
src/​TechHub.Infrastructure/​Services/​ContentProcessing/​TimelineEventParser.cs Validates and normalizes extracted events.
src/​TechHub.Infrastructure/​Services/​ContentProcessing/​TimelineBackfillService.cs Scans stored articles resumably.
src/​TechHub.Infrastructure/​Services/​ContentProcessing/​ContentProcessingService.cs Persists events and propagates interactive mode.
src/​TechHub.Infrastructure/​Services/​ContentProcessing/​ClassificationSchema.cs Defines strict classification and timeline schemas.
src/​TechHub.Infrastructure/​Services/​ContentProcessing/​ArticleContentService.cs Preserves interactive mode during enrichment.
src/​TechHub.Infrastructure/​Services/​AiRequestBodyBuilder.cs Builds profile-based completion requests.
src/​TechHub.Infrastructure/​Services/​AiCompletionClient.cs Adds v1 requests, Flex handling and logging.
src/​TechHub.Infrastructure/​Repositories/​TimelineEventRepository.cs Stores events and tracks extraction progress.
src/​TechHub.Infrastructure/​Repositories/​SectionRoundupRepository.cs Loads new metadata and upcoming events.
src/​TechHub.Infrastructure/​Repositories/​ContentItemWriteRepository.cs Serializes versioned roundup metadata.
src/​TechHub.Infrastructure/​Data/​Resources/​roundup-news-writer-system.md Defines single-pass roundup writing.
src/​TechHub.Infrastructure/​Data/​Resources/​roundup-narrative-enhancer-system.md Removes the enhancement prompt.
src/​TechHub.Infrastructure/​Data/​Resources/​roundup-metadata-generator-system.md Simplifies metadata instructions.
src/​TechHub.Infrastructure/​Data/​Resources/​roundup-condenser-system.md Removes the condensing prompt.
src/​TechHub.Infrastructure/​Data/​Resources/​content-timeline-rules.md Defines milestone extraction rules.
src/​TechHub.Infrastructure/​Data/​Resources/​content-timeline-extract-system.md Adds timeline-only extraction instructions.
src/​TechHub.Infrastructure/​Data/​Resources/​content-rewrite-system.md Defines standalone Markdown rewriting.
src/​TechHub.Infrastructure/​Data/​Migrations/​postgres/​049_content_timeline_events.sql Creates timeline and extraction tables.
src/​TechHub.Infrastructure/​AGENTS.md Updates the prompt resource inventory.
src/​TechHub.Core/​Models/​ContentProcessing/​TimelineExtractionResult.cs Models extraction results and failures.
src/​TechHub.Core/​Models/​ContentProcessing/​TimelineEvent.cs Defines extracted lifecycle events.
src/​TechHub.Core/​Models/​ContentProcessing/​TimelineBackfill.cs Defines backfill requests and results.
src/​TechHub.Core/​Models/​ContentProcessing/​StoredTimelineEvent.cs Models persisted events.
src/​TechHub.Core/​Models/​ContentProcessing/​RoundupMetadata.cs Adds versioned facts and section roles.
src/​TechHub.Core/​Models/​ContentProcessing/​RoundupDateEvent.cs Models roundup date entries.
src/​TechHub.Core/​Models/​ContentProcessing/​RoundupArticle.cs Carries new metadata and primary sections.
src/​TechHub.Core/​Models/​ContentProcessing/​RawFeedItem.cs Adds interactive processing mode.
src/​TechHub.Core/​Models/​ContentProcessing/​ProcessedContentItem.cs Preserves timeline data across copies.
src/​TechHub.Core/​Models/​Admin/​ContentProcessingJob.cs Adds the timeline job type.
src/​TechHub.Core/​Interfaces/​ITimelineEventRepository.cs Defines timeline persistence operations.
src/​TechHub.Core/​Interfaces/​ITimelineBackfillService.cs Defines backfill execution.
src/​TechHub.Core/​Interfaces/​ISectionRoundupRepository.cs Adds upcoming-event queries.
src/​TechHub.Core/​Interfaces/​IAiCategorizationService.cs Separates classification and timeline extraction.
src/​TechHub.Core/​Configuration/​AiProfileOptions.cs Defines per-step model profiles.
src/​TechHub.Core/​Configuration/​AiCategorizationOptions.cs Adds profile and Flex settings.
src/​TechHub.Api/​Services/​TimelineBackfillBackgroundService.cs Runs manual, cancellable backfill jobs.
src/​TechHub.Api/​Program.cs Registers services and extends AI timeouts.
src/​TechHub.Api/​Endpoints/​AdminRequests.cs Defines backfill request options.
src/​TechHub.Api/​Endpoints/​AdminEndpoints.cs Adds backfill triggering and transcript event writes.
src/​TechHub.Api/​appsettings.json Configures profile defaults and Flex retries.
scripts/​Setup-UserSecrets.ps1 Copies production AI profile settings.
infra/​pr-applications.bicep Removes the legacy deployment parameter.
infra/​parameters/​prod-infrastructure.bicepparam Removes the legacy capacity override.
infra/​modules/​openai.bicep Deploys multiple models sequentially.
infra/​modules/​api.bicep Generates profile-specific app settings.
infra/​infrastructure.bicep Uses module-defined model capacities.
infra/​applications.bicep Selects production models and Flex tiers.
docs/​database.md Documents timeline tables and metadata.
docs/​admin.md Documents backfill and cancellation endpoints.

Review effort: Balanced

Comment thread src/TechHub.Infrastructure/Repositories/TimelineEventRepository.cs Outdated
Comment thread src/TechHub.Infrastructure/Repositories/TimelineEventRepository.cs
Comment thread src/TechHub.Infrastructure/Services/ContentProcessing/ClassificationSchema.cs Outdated
…imeline backfill (#581)

Timeline extraction shared the Classification profile, which moved to gpt-6.1-sol.
The backfill only extracts dated facts, so it does not need that strictness and
costs about 20x less on luna.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 20:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Partial timeline writes can leave inconsistent saved data, and an uncancelled HTTP timeout can abort the entire backfill.

6 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Handle non-cancellation HTTP timeouts per article

src/​TechHub.Infrastructure/​Services/​ContentProcessing/​TimelineBackfillService.cs:105

An HTTP timeout can surface as TaskCanceledException even when ct is not cancelled. AiCategorizationService.SendWithRetryAsync lets it escape on the final attempt, and this filter excludes it from per-article error handling. One timed-out article therefore stops the entire backfill and the background service labels it an admin abort. Propagate cancellation only when ct is cancelled; otherwise log/count the failure and continue.

Review effort: Balanced

Comment thread src/TechHub.Infrastructure/Services/ContentProcessing/ContentProcessingService.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 06:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The rewrite’s excerpt assumption can omit displayed context, and inconsistent publication timezones can shift extracted relative dates.

6 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Internal detail pages omit the excerpt before article content

src/​TechHub.Infrastructure/​Data/​Resources/​content-rewrite-system.md:34

The excerpt is not displayed above the body on internal detail pages: ContentItemDetail.razor:15-38 renders it only for externally linked items, and ContentRepository.RenderHtmlIfNeeded renders only Content. Following this instruction can therefore leave opening context out of the displayed article. Keep the rewritten body self-contained, or render the excerpt above the body for these items.

Medium severity UTC publication dates shift Brussels-local timeline dates

src/​TechHub.Infrastructure/​Services/​ContentProcessing/​TimelineBackfillService.cs:76

FromUnixTimeSeconds produces a UTC value, which BuildTimelineExtractionPrompt formats directly as yyyy-MM-dd. Publication-sourced events, however, use the Brussels date. For an article published at 23:30 UTC on 14 March, the prompt says 14 March while the parser uses 15 March. This can shift model-resolved dates such as “tomorrow” by a day. Convert the timestamp to Europe/Brussels before passing it to extraction, and add a regression test around local midnight.

Review effort: Balanced

…rticle, configured sections, exact link matching, roundup link finalization

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 07:41
Copilot AI balanced review requested due to automatic review settings October 22, 2026 09:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved stale-event writes, backfill eligibility, timeout handling and coarse-date filtering can leave timeline data incorrect or incomplete.

2 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Use period-end dates for coarse-precision roundup event filtering

src/​TechHub.Infrastructure/​Repositories/​SectionRoundupRepository.cs:237

Month- and quarter-precision events use the first day of their period as a placeholder, not an exact event date. For example, a launch announced for November is stored as November 1 and excluded from a roundup ending November 1, even though it could still be upcoming. Compare the represented period's end against the week end for coarse dates, while retaining the exact comparison for day precision. Apply the same rule in RoundupBlocksBuilder.BuildDatesToKnow, which repeats this filter, and cover overlapping months and quarters with regression tests.

Medium severity Invalidate extraction eligibility after edits without event replacement

src/​TechHub.Infrastructure/​Repositories/​TimelineEventRepository.cs:136

An ordinary admin edit calls UpdateEditDataAsync without a timeline replacement. Changing a scanned article's content, excerpt or publication date therefore leaves its old events and current-version extraction marker intact. This eligibility check skips that article on subsequent non-forced backfills, so the dashboard's Backfill Timeline action cannot repair the stale events. Invalidate the extraction marker when an edit changes extraction inputs without replacing events, or persist and compare the extracted article revision. Add a regression test that edits an article after a completed scan and verifies that it becomes eligible again.

7 resolved since last review

Review effort: Balanced

Comment thread src/TechHub.Infrastructure/Services/ContentProcessing/TimelineBackfillService.cs Outdated
…dits, treat timeouts as per-article failures, fix test warnings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 09:52
…ent enrichment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Timeline freshness and date-handling defects, plus empty profile-setting synchronization, remain unresolved.

1 open issue
4 previously missed issues

In code that hasn't changed since last review

Medium severity Preserve explicitly empty reasoning effort settings

scripts/​Setup-UserSecrets.ps1:295

An explicitly empty ReasoningEffort means that AiRequestBodyBuilder should omit reasoning_effort. Skipping it here instead retains the low/medium value from appsettings.json, or an old user secret even with -Force, so local requests differ from the production profile. Distinguish absent settings from explicitly empty ones and allow an empty reasoning setting through both this loop and Set-Secret. Update the PowerShell test that currently requires this value to be skipped.

Medium severity Rebase publication events when capping future publication dates

src/​TechHub.Core/​Models/​ContentProcessing/​ProcessedContentItem.cs:170

ContentProcessingService uses WithDateEpoch to cap future-dated articles to the processing date, but this copy keeps publication-sourced events on the original future date assigned during classification. Capping an article from 20 January to 15 January thus leaves its "available now" event on 20 January, so an already-live release can appear as upcoming. Rebase publication-sourced events to the new Europe/Brussels date while preserving explicit dates, and extend the future-date regression test.

Medium severity Convert publication timestamps to Brussels time before extraction

src/​TechHub.Infrastructure/​Services/​ContentProcessing/​TimelineBackfillService.cs:76

FromUnixTimeSeconds returns a UTC-offset value, and BuildTimelineExtractionPrompt formats that value directly as DATE. A publication at 2026-03-14 23:30 UTC therefore reaches the model as March 14, while TimelineEventParser.GetBrusselsDate anchors publication-derived events to March 15. This gives relative dates such as "tomorrow" an inconsistent reference day. Convert the publication timestamp to Europe/Brussels before sending it to extraction, and add a midnight-boundary regression test.

Medium severity Handle month and quarter periods in upcoming event filtering

src/​TechHub.Infrastructure/​Services/​RoundupGeneration/​RoundupBlocksBuilder.cs:142

Month and quarter dates represent periods, not exact days. An October article announcing GA "later in Q4 2026" is normalized to 2026-10-01, so a roundup ending 2026-10-25 drops that still-upcoming release. Determine whether the represented period extends beyond the roundup week rather than comparing only its first day. Apply the same rule in SectionRoundupRepository.GetUpcomingDateEventsAsync, whose SQL currently removes these events before the builder sees them. Keep the coarse display precision and add current-month and current-quarter regression cases.

2 resolved since last review

Review effort: Balanced

Comment thread src/TechHub.Infrastructure/Repositories/ContentRepository.cs
Copilot AI balanced review requested due to automatic review settings October 22, 2026 10:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Local profile synchronization loses explicit empty settings, and timeline backfill can supply the wrong publication day near Brussels midnight.

1 open issue
2 previously missed issues

In code that hasn't changed since last review

Medium severity Empty reasoning effort is incorrectly treated as missing

scripts/​Setup-UserSecrets.ps1:295

An explicitly empty ReasoningEffort means “omit the parameter,” but this check treats it as a missing setting. For example, the empty Rewrite effort in the updated test leaves local configuration inheriting "low" from appsettings.json, while production omits the parameter. -Force cannot correct this because both this check and Set-Secret reject empty values. Preserve present-but-empty reasoning settings while still skipping missing settings, and update the regression test to verify that the local request omits reasoning_effort.

Medium severity Prompt publication date ignores Brussels timezone

src/​TechHub.Infrastructure/​Services/​ContentProcessing/​TimelineBackfillService.cs:76

FromUnixTimeSeconds returns a UTC-offset value, and BuildTimelineExtractionPrompt formats PublishedAt directly as yyyy-MM-dd. Near midnight, the prompt therefore uses a different publication day from TimelineEventParser.GetBrusselsDate: 14 March at 23:30 UTC is 15 March in Brussels. Relative dates such as “tomorrow” can consequently be extracted one day early. Convert this value to Europe/Brussels before passing it to extraction, and add a regression test asserting the prompt date at this boundary.

Review effort: Balanced

…cle text, use Path.Join in evaluation test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 10:41
Comment thread tests/TechHub.Infrastructure.Tests/Evaluation/ModelEvaluationTests.cs Dismissed
Comment thread tests/TechHub.Infrastructure.Tests/Evaluation/ModelEvaluationTests.cs Dismissed
Comment thread tests/TechHub.Infrastructure.Tests/Evaluation/ModelEvaluationTests.cs Dismissed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The production model switch, transactional database changes, unresolved correctness findings, and missing final E2E run require human sign-off.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Preserve explicitly empty reasoning effort settings

scripts/​Setup-UserSecrets.ps1:295

An explicitly empty ReasoningEffort means “omit reasoning_effort”, but skipping it here leaves the low/medium value from appsettings.json active, or preserves an older user-secret value. A deployed profile that omits this parameter is therefore copied locally with it enabled, potentially causing rejected requests for a model that does not support it. Distinguish missing settings from explicitly empty reasoning settings and preserve the latter, including through Set-Secret. Update the test that currently expects an empty reasoning setting to be skipped.

1 resolved since last review

Review effort: Balanced

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Explicit empty profile overrides are lost during local synchronization, and publication-derived event dates remain stale when article dates are capped.

2 open issues
1 resolved since last review

Review effort: Balanced

Comment thread scripts/Setup-UserSecrets.ps1 Outdated
Comment thread src/TechHub.Core/Models/ContentProcessing/ProcessedContentItem.cs
…ecrets and rebase publication-dated timeline events when capping future dates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 15:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The production model switch and cross-layer persistence changes require human validation, especially with an unresolved invalidation gap and no final-code E2E run.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Invalidate extraction state after markdown review approval

src/​TechHub.Infrastructure/​Repositories/​ContentRepository.cs:1707

This invalidation only covers edits through UpdateEditDataAsync. Approving a markdown review still writes content_items.content directly in ContentReviewRepository.ApplyChangeAsync (lines 288–308), including replacement text edited by an admin. For an already-scanned article, that leaves the old events and current-version extraction marker intact, so an ordinary timeline backfill skips the changed text. Invalidate the events and extraction marker in the review-approval transaction when the content changes, using the same parent-row locking as this edit path. Add a scan → markdown review approval → ordinary backfill regression test.

2 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 22, 2026 17:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The production model switch and transactional timeline workflow need human rollout validation, especially without a final end-to-end test run.

1 open issue

Review effort: Balanced

Comment on lines +432 to +433
background: color-mix(in srgb, #8b5cf6 15%, transparent);
color: #c4b5fd;

This branch has not been deployed

No deployments
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.

Reduce AI cost: three-model pipeline (gpt-6-luna classify → gpt-6.1-sol rewrite → gpt-6-astra/6.1-sol roundups, Flex)

2 participants