fix(encyclopedia): tighten assign tests and first-paragraph handling
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -150,7 +150,12 @@ public sealed partial class AssignNotesUseCase(ITextModel model, EncyclopediaSet
|
||||
log.LogWarning("Dropping new article {Slug}: empty scope", slug);
|
||||
return null;
|
||||
}
|
||||
return new PlannedArticle(slug, Text(o["titleDe"]) ?? slug, Text(o["titleEn"]) ?? slug, category, scope, []);
|
||||
if (Text(o["titleDe"]) is not { } titleDe || Text(o["titleEn"]) is not { } titleEn)
|
||||
{
|
||||
log.LogWarning("Dropping new article {Slug}: missing title", slug);
|
||||
return null;
|
||||
}
|
||||
return new PlannedArticle(slug, titleDe, titleEn, category, scope, []);
|
||||
}
|
||||
|
||||
private static string? Text(JsonNode? n) =>
|
||||
@@ -177,8 +182,8 @@ public sealed partial class AssignNotesUseCase(ITextModel model, EncyclopediaSet
|
||||
private static string FirstParagraph(string body)
|
||||
{
|
||||
var lines = body.Replace("\r", "").Split('\n')
|
||||
.SkipWhile(l => l.Length == 0 || l.TrimStart().StartsWith('#'))
|
||||
.TakeWhile(l => l.Trim().Length > 0);
|
||||
.SkipWhile(l => string.IsNullOrWhiteSpace(l) || l.TrimStart().StartsWith('#'))
|
||||
.TakeWhile(l => !string.IsNullOrWhiteSpace(l));
|
||||
var text = Flatten(string.Join(' ', lines)).Trim();
|
||||
return text.Length > FirstParagraphChars ? text[..FirstParagraphChars] : text;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Wiki.Application.Encyclopedia;
|
||||
|
||||
/// <summary>
|
||||
/// Prompt texts of the encyclopedia. <see cref="Version"/> is bumped by hand whenever a prompt changes,
|
||||
/// so that a prompt fix retries earlier rejections and failed plans.
|
||||
/// so that a prompt fix retries earlier rejections and failed batches.
|
||||
/// </summary>
|
||||
public static class Prompts
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ public class AssignNotesUseCaseTests
|
||||
[Fact]
|
||||
public async Task Assign_Truncated_IsUnusable()
|
||||
{
|
||||
var model = new FakeTextModel().Reply("""{"new":[],"notes":[""", truncated: true);
|
||||
var model = new FakeTextModel().Reply("""{"new":[],"notes":[{"note":"a","skip":true}]}""", truncated: true);
|
||||
|
||||
var outcome = await UseCase(model).AssignAsync(Batch("a"), [], ArticlePlan.Empty(), CancellationToken.None);
|
||||
|
||||
@@ -119,14 +119,53 @@ public class AssignNotesUseCaseTests
|
||||
var batch = Batch(Enumerable.Range(0, 20).Select(i => $"note-{i:00}").ToArray());
|
||||
var model = new FakeTextModel().Reply("""{"new":[],"notes":[]}""");
|
||||
|
||||
await UseCase(model).AssignAsync(batch, articles.Take(20).ToArray(), plan, CancellationToken.None);
|
||||
await UseCase(model).AssignAsync(batch, articles.Take(100).ToArray(), plan, CancellationToken.None);
|
||||
|
||||
var user = model.Calls[0].User;
|
||||
Assert.Equal(20, user.Split('\n').Count(l => l.StartsWith("note-")));
|
||||
Assert.True(user.Split('\n').Count(l => l.StartsWith("article-")) <= 20);
|
||||
Assert.Equal(20, user.Split('\n').Count(l => l.StartsWith("article-")));
|
||||
Assert.DoesNotContain("article-20 ", user);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Assign_FirstParagraph_SkipsHeadingsAndBlankLines()
|
||||
{
|
||||
var note = PlanArticlesUseCaseTests.Note("a") with { Body = "# T\n\n \nfirst line\ncontinued\n\nsecond para" };
|
||||
var model = new FakeTextModel().Reply("""{"new":[],"notes":[]}""");
|
||||
|
||||
await UseCase(model).AssignAsync([note], [], ArticlePlan.Empty(), CancellationToken.None);
|
||||
|
||||
var line = model.Calls[0].User.Split('\n').Single(l => l.StartsWith("a | "));
|
||||
Assert.EndsWith(" | first line continued", line);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Assign_FirstParagraph_CutTo300()
|
||||
{
|
||||
var note = PlanArticlesUseCaseTests.Note("a") with { Body = new string('y', 500) };
|
||||
var model = new FakeTextModel().Reply("""{"new":[],"notes":[]}""");
|
||||
|
||||
await UseCase(model).AssignAsync([note], [], ArticlePlan.Empty(), CancellationToken.None);
|
||||
|
||||
var line = model.Calls[0].User.Split('\n').Single(l => l.StartsWith("a | "));
|
||||
Assert.EndsWith(" | " + new string('y', 300), line);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Assign_NewWithoutTitle_Dropped()
|
||||
{
|
||||
var heating = Article("heating");
|
||||
var model = new FakeTextModel().Reply("""
|
||||
{"new":[{"slug":"garden","titleDe":"G","category":"room","scope":"s"}],
|
||||
"notes":[{"note":"a","articles":["garden","heating"]}]}
|
||||
""");
|
||||
|
||||
var outcome = await UseCase(model).AssignAsync(Batch("a"), [heating], PlanWith(heating), CancellationToken.None);
|
||||
|
||||
Assert.Empty(outcome.New);
|
||||
Assert.Equal(["heating"], outcome.Assignments["a"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Assign_ModelUnavailable_Propagates()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user