Phase 21O - Story Intelligence Usability and Continuity Refinement
This commit is contained in:
parent
f80734a80c
commit
63f427066d
@ -52,6 +52,7 @@ var tests = new (string Name, Action Test)[]
|
|||||||
("Illustration demand archetypes stay broad and use active template", IllustrationDemandArchetypesStayBroadAndUseActiveTemplate),
|
("Illustration demand archetypes stay broad and use active template", IllustrationDemandArchetypesStayBroadAndUseActiveTemplate),
|
||||||
("Story Intelligence evidence extraction prevents observed attribute leakage", StoryIntelligenceEvidenceExtractionPreventsObservedAttributeLeakage),
|
("Story Intelligence evidence extraction prevents observed attribute leakage", StoryIntelligenceEvidenceExtractionPreventsObservedAttributeLeakage),
|
||||||
("Illustration assignment repair enforces explicit evidence groups and strict subtypes", IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictSubtypes),
|
("Illustration assignment repair enforces explicit evidence groups and strict subtypes", IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictSubtypes),
|
||||||
|
("Phase 21O preserves adult defaults and real-mode continuity", Phase21OPreservesAdultDefaultsAndRealModeContinuity),
|
||||||
("Scan review post supports full-book form submissions", ScanReviewPostSupportsFullBookFormSubmissions),
|
("Scan review post supports full-book form submissions", ScanReviewPostSupportsFullBookFormSubmissions),
|
||||||
("Story Intelligence experience boot does not serialise live model", StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel),
|
("Story Intelligence experience boot does not serialise live model", StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel),
|
||||||
("Live visualisation strips illustration diagnostics", LiveVisualisationStripsIllustrationDiagnostics),
|
("Live visualisation strips illustration diagnostics", LiveVisualisationStripsIllustrationDiagnostics),
|
||||||
@ -581,7 +582,7 @@ static void IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictS
|
|||||||
Assert(femaleInstructor.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"female instructor should be Feminine, got {femaleInstructor.Presentation}.");
|
Assert(femaleInstructor.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"female instructor should be Feminine, got {femaleInstructor.Presentation}.");
|
||||||
Assert(maleExaminers.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"male examiners should be Masculine, got {maleExaminers.Presentation}.");
|
Assert(maleExaminers.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"male examiners should be Masculine, got {maleExaminers.Presentation}.");
|
||||||
Assert(lad.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"lad should be Masculine, got {lad.Presentation}.");
|
Assert(lad.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"lad should be Masculine, got {lad.Presentation}.");
|
||||||
Assert(lad.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Child, $"lad should infer a young age band, got {lad.AgeBand}.");
|
Assert(lad.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Unknown, $"lad should not default to Child without explicit child-age evidence, got {lad.AgeBand}.");
|
||||||
Assert(girl.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"girl should be Feminine, got {girl.Presentation}.");
|
Assert(girl.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"girl should be Feminine, got {girl.Presentation}.");
|
||||||
Assert(girl.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Child, $"schoolgirl should infer Child, got {girl.AgeBand}.");
|
Assert(girl.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Child, $"schoolgirl should infer Child, got {girl.AgeBand}.");
|
||||||
Assert(kevin.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"Kevin should not inherit feminine presentation from his instructor, got {kevin.Presentation}.");
|
Assert(kevin.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"Kevin should not inherit feminine presentation from his instructor, got {kevin.Presentation}.");
|
||||||
@ -597,7 +598,7 @@ static void IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictS
|
|||||||
Assert(!StoryIntelligenceIllustrationCompatibility.LocationCompatible("Bathroom", "LaundryUtilityRoom"), "Bathroom must not match laundry room.");
|
Assert(!StoryIntelligenceIllustrationCompatibility.LocationCompatible("Bathroom", "LaundryUtilityRoom"), "Bathroom must not match laundry room.");
|
||||||
Assert(!StoryIntelligenceIllustrationCompatibility.LocationCompatible("Road", "CarPark"), "Road must not match car park.");
|
Assert(!StoryIntelligenceIllustrationCompatibility.LocationCompatible("Road", "CarPark"), "Road must not match car park.");
|
||||||
Assert(!StoryIntelligenceIllustrationCompatibility.LocationCompatible("WaitingRoom", "FlatInterior"), "Waiting room must not match flat interior.");
|
Assert(!StoryIntelligenceIllustrationCompatibility.LocationCompatible("WaitingRoom", "FlatInterior"), "Waiting room must not match flat interior.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("Ford Fiesta") == "Car", "Ford Fiesta should classify as Car.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("Ford Fiesta") == "Hatchback", "Ford Fiesta should classify as Hatchback.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("Car") == "Car", "Car should classify as Car.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("Car") == "Car", "Car should classify as Car.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("supercharger") == "CarPart", "Supercharger should not classify as full car.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("supercharger") == "CarPart", "Supercharger should not classify as full car.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("biscuit tin") == "Tin", "Biscuit tin should not classify as Photograph.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("biscuit tin") == "Tin", "Biscuit tin should not classify as Photograph.");
|
||||||
@ -612,6 +613,28 @@ static void IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictS
|
|||||||
Assert(Rejected(beth, adult).Any(reason => reason.Contains("age hard mismatch", StringComparison.OrdinalIgnoreCase)), "Existing adult assignment must be discarded when Beth evidence says YoungTeen.");
|
Assert(Rejected(beth, adult).Any(reason => reason.Contains("age hard mismatch", StringComparison.OrdinalIgnoreCase)), "Existing adult assignment must be discarded when Beth evidence says YoungTeen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Phase21OPreservesAdultDefaultsAndRealModeContinuity()
|
||||||
|
{
|
||||||
|
var unknownNamed = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Sarah", ["Sarah watches."], []);
|
||||||
|
var explicitGirl = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Mia", ["schoolgirl"], []);
|
||||||
|
var teacher = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Mrs Patterson", ["Mrs Patterson is a teacher."], []);
|
||||||
|
var examiner = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("driving examiner", ["female driving examiner"], []);
|
||||||
|
|
||||||
|
Assert(unknownNamed.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Unknown, $"Unknown age should remain Unknown before illustration fallback, got {unknownNamed.AgeBand}.");
|
||||||
|
Assert(explicitGirl.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Child, $"Schoolgirl should remain explicit child evidence, got {explicitGirl.AgeBand}.");
|
||||||
|
Assert(teacher.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Adult, $"Teacher title/role should infer Adult, got {teacher.AgeBand}.");
|
||||||
|
Assert(examiner.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"Female examiner should infer Feminine, got {examiner.Presentation}.");
|
||||||
|
Assert(examiner.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Adult, $"Driving examiner should infer Adult, got {examiner.AgeBand}.");
|
||||||
|
Assert(StoryIntelligenceIllustrationCompatibility.LocationType("Bristol Road") == "Road", "Road names should classify as Road.");
|
||||||
|
Assert(StoryIntelligenceIllustrationCompatibility.LocationType("Cock Hill Lane") == "Road", "Lane names should classify as Road.");
|
||||||
|
Assert(!StoryIntelligenceIllustrationCompatibility.AssetCompatible("Hatchback", "Car"), "Hatchbacks must not borrow generic car artwork.");
|
||||||
|
Assert(!StoryIntelligenceIllustrationCompatibility.AssetCompatible("SportsCar", "Hatchback"), "Vehicle subtypes should remain strict.");
|
||||||
|
|
||||||
|
var view = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "PlotLine", "Views", "Development", "StoryIntelligenceExperience.cshtml"));
|
||||||
|
Assert(view.Contains("Return to Import", StringComparison.Ordinal), "Real visualisation should expose a return-to-import link.");
|
||||||
|
Assert(view.Contains("Model.Mode == \"simulation\"", StringComparison.Ordinal), "Prototype controls should be gated to simulation mode.");
|
||||||
|
}
|
||||||
|
|
||||||
static IllustrationCharacterMetadata Metadata(string code, string ageBand, string presentation, bool isUnknownFigure = false)
|
static IllustrationCharacterMetadata Metadata(string code, string ageBand, string presentation, bool isUnknownFigure = false)
|
||||||
=> new(
|
=> new(
|
||||||
ageBand,
|
ageBand,
|
||||||
|
|||||||
@ -77,9 +77,13 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
presentation = guardedPresentation.Value;
|
presentation = guardedPresentation.Value;
|
||||||
presentationConfidence = guardedPresentation.Confidence;
|
presentationConfidence = guardedPresentation.Confidence;
|
||||||
var explicitAge = ExplicitAgeBand(scopedText);
|
var explicitAge = ExplicitAgeBand(scopedText);
|
||||||
|
if (explicitAge.Value == AgeBands.Unknown)
|
||||||
|
{
|
||||||
|
explicitAge = EntityIdentityAgeBand(name, identityText);
|
||||||
|
}
|
||||||
var relationshipAge = RelationshipAgeBand(name, scopedText);
|
var relationshipAge = RelationshipAgeBand(name, scopedText);
|
||||||
var namedAge = NameAgeBand(name);
|
var namedAge = NameAgeBand(name);
|
||||||
var age = FirstKnown(relationshipAge.Value, explicitAge.Value, namedAge.Value, AgeBands.Unknown);
|
var age = FirstKnown(explicitAge.Value, relationshipAge.Value, namedAge.Value, AgeBands.Unknown);
|
||||||
var ageConfidence = new[] { explicitAge.Confidence, relationshipAge.Confidence, namedAge.Confidence }.Max();
|
var ageConfidence = new[] { explicitAge.Confidence, relationshipAge.Confidence, namedAge.Confidence }.Max();
|
||||||
var attributeText = EntityAttributeText(entityText, name);
|
var attributeText = EntityAttributeText(entityText, name);
|
||||||
var hairColour = HairColour(attributeText);
|
var hairColour = HairColour(attributeText);
|
||||||
@ -171,7 +175,9 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
rejected.Add($"presentation hard mismatch: wanted {evidence.Presentation}, candidate {candidate.Presentation}");
|
rejected.Add($"presentation hard mismatch: wanted {evidence.Presentation}, candidate {candidate.Presentation}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ExplicitKnown(evidence.HairColour, evidence.AgeConfidence) && KnownMismatch(evidence.HairColour, candidate.HairColour))
|
if (!string.Equals(evidence.HairColour, HairColours.Unknown, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.Equals(candidate.HairColour, HairColours.Unknown, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& KnownMismatch(evidence.HairColour, candidate.HairColour))
|
||||||
{
|
{
|
||||||
rejected.Add($"hair colour hard mismatch: wanted {evidence.HairColour}, candidate {candidate.HairColour}");
|
rejected.Add($"hair colour hard mismatch: wanted {evidence.HairColour}, candidate {candidate.HairColour}");
|
||||||
}
|
}
|
||||||
@ -189,7 +195,7 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
var text = (value ?? string.Empty).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(" ", string.Empty, StringComparison.OrdinalIgnoreCase).ToLowerInvariant();
|
var text = (value ?? string.Empty).Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(" ", string.Empty, StringComparison.OrdinalIgnoreCase).ToLowerInvariant();
|
||||||
if (text.Contains("youngteen") || text.Contains("13") || text.Contains("14") || text.Contains("15") || text.Contains("fifteen")) return AgeBands.YoungTeen;
|
if (text.Contains("youngteen") || text.Contains("13") || text.Contains("14") || text.Contains("15") || text.Contains("fifteen")) return AgeBands.YoungTeen;
|
||||||
if (text.Contains("olderteen") || text.Contains("teen") || text.Contains("16") || text.Contains("17") || text.Contains("18") || text.Contains("19")) return AgeBands.OlderTeen;
|
if (text.Contains("olderteen") || text.Contains("teen") || text.Contains("16") || text.Contains("17") || text.Contains("18") || text.Contains("19")) return AgeBands.OlderTeen;
|
||||||
if (text.Contains("child") || text.Contains("boy") || text.Contains("girl")) return AgeBands.Child;
|
if (text.Contains("child")) return AgeBands.Child;
|
||||||
if (text.Contains("youngadult")) return AgeBands.YoungAdult;
|
if (text.Contains("youngadult")) return AgeBands.YoungAdult;
|
||||||
if (text.Contains("middleaged") || text.Contains("matureadult") || text.Contains("parent") || text.Contains("mother") || text.Contains("father") || text.Contains("aunt") || text.Contains("uncle")) return AgeBands.MatureAdult;
|
if (text.Contains("middleaged") || text.Contains("matureadult") || text.Contains("parent") || text.Contains("mother") || text.Contains("father") || text.Contains("aunt") || text.Contains("uncle")) return AgeBands.MatureAdult;
|
||||||
if (text.Contains("adult")) return AgeBands.Adult;
|
if (text.Contains("adult")) return AgeBands.Adult;
|
||||||
@ -251,7 +257,7 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
if (ContainsAny(text, "office", "desk", "study", "library")) return "Office";
|
if (ContainsAny(text, "office", "desk", "study", "library")) return "Office";
|
||||||
if (text.Contains("school", StringComparison.OrdinalIgnoreCase)) return "School";
|
if (text.Contains("school", StringComparison.OrdinalIgnoreCase)) return "School";
|
||||||
if (text.Contains("waiting room", StringComparison.OrdinalIgnoreCase)) return "WaitingRoom";
|
if (text.Contains("waiting room", StringComparison.OrdinalIgnoreCase)) return "WaitingRoom";
|
||||||
if (ContainsAny(text, "road", "street", "lane", "drive", "motorway", "bridge")) return "Road";
|
if (ContainsAny(text, "road", "street", "lane", "drive", "close", "avenue", "boulevard", "motorway", "bridge", "roundabout", "footpath", "track")) return "Road";
|
||||||
if (ContainsAny(text, "car park", "parking")) return "CarPark";
|
if (ContainsAny(text, "car park", "parking")) return "CarPark";
|
||||||
if (text.Contains("shop", StringComparison.OrdinalIgnoreCase)) return "Shop";
|
if (text.Contains("shop", StringComparison.OrdinalIgnoreCase)) return "Shop";
|
||||||
if (text.Contains("hospital", StringComparison.OrdinalIgnoreCase)) return "Hospital";
|
if (text.Contains("hospital", StringComparison.OrdinalIgnoreCase)) return "Hospital";
|
||||||
@ -272,7 +278,11 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
if (ContainsAny(text, "driving licence", "driver licence", "provisional licence", "license", "licence", "pass certificate", "certificate", "paperwork")) return "Document";
|
if (ContainsAny(text, "driving licence", "driver licence", "provisional licence", "license", "licence", "pass certificate", "certificate", "paperwork")) return "Document";
|
||||||
if (ContainsAny(text, "ring", "necklace", "jewellery", "jewelry")) return "Jewellery";
|
if (ContainsAny(text, "ring", "necklace", "jewellery", "jewelry")) return "Jewellery";
|
||||||
if (ContainsAny(text, "gun", "pistol", "rifle", "weapon")) return "Weapon";
|
if (ContainsAny(text, "gun", "pistol", "rifle", "weapon")) return "Weapon";
|
||||||
if (ContainsAny(padded, " ford fiesta ", " fiesta ", " motor car ", " car ", " vehicle ")) return "Car";
|
if (ContainsAny(padded, " hatchback ", " vw golf ", " volkswagen golf ", " golf gti ", " ford fiesta ", " fiesta ")) return "Hatchback";
|
||||||
|
if (ContainsAny(padded, " family car ", " estate car ", " saloon ")) return "FamilyCar";
|
||||||
|
if (ContainsAny(padded, " sports car ", " tr6 ", " roadster ")) return "SportsCar";
|
||||||
|
if (ContainsAny(padded, " van ", " transit ")) return "Van";
|
||||||
|
if (ContainsAny(padded, " motor car ", " car ", " vehicle ")) return "Car";
|
||||||
if (ContainsAny(text, "supercharger", "engine part")) return "CarPart";
|
if (ContainsAny(text, "supercharger", "engine part")) return "CarPart";
|
||||||
if (ContainsAny(text, "biscuit tin", "tin")) return "Tin";
|
if (ContainsAny(text, "biscuit tin", "tin")) return "Tin";
|
||||||
if (ContainsAny(text, "parcel", "package")) return "Parcel";
|
if (ContainsAny(text, "parcel", "package")) return "Parcel";
|
||||||
@ -326,22 +336,40 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
if (ContainsAny(text, "13", "thirteen", "14", "fourteen")) return new(AgeBands.YoungTeen, 0.98m);
|
if (ContainsAny(text, "13", "thirteen", "14", "fourteen")) return new(AgeBands.YoungTeen, 0.98m);
|
||||||
if (ContainsAny(text, "16", "sixteen", "17", "seventeen", "18", "eighteen", "19", "nineteen")) return new(AgeBands.OlderTeen, 0.98m);
|
if (ContainsAny(text, "16", "sixteen", "17", "seventeen", "18", "eighteen", "19", "nineteen")) return new(AgeBands.OlderTeen, 0.98m);
|
||||||
if (ContainsAny(text, "toddler", "baby", "infant", "newborn")) return new(AgeBands.Child, 0.98m);
|
if (ContainsAny(text, "toddler", "baby", "infant", "newborn")) return new(AgeBands.Child, 0.98m);
|
||||||
if (ContainsAny(text, "child", "young girl", "young boy", "schoolboy", "schoolgirl", " boy ", " girl ", " lad ")) return new(AgeBands.Child, 0.86m);
|
if (ContainsAny(text, "child", "young child", "schoolboy", "schoolgirl", "ten-year-old", "ten year old", "aged 10", "aged 11", "aged 12")) return new(AgeBands.Child, 0.9m);
|
||||||
if (ContainsAny(text, "teenager", "teenage")) return new(AgeBands.OlderTeen, 0.86m);
|
if (ContainsAny(text, "teenager", "teenage")) return new(AgeBands.OlderTeen, 0.86m);
|
||||||
if (ContainsAny(text, "young adult", "young woman", "young man")) return new(AgeBands.YoungAdult, 0.82m);
|
if (ContainsAny(text, "young adult", "young woman", "young man")) return new(AgeBands.YoungAdult, 0.82m);
|
||||||
if (ContainsAny(text, "middle-aged", "middle aged", "mature adult")) return new(AgeBands.MatureAdult, 0.86m);
|
if (ContainsAny(text, "middle-aged", "middle aged", "mature adult")) return new(AgeBands.MatureAdult, 0.86m);
|
||||||
|
if (ContainsAny(text, " mr ", " mr. ", " mrs ", " mrs. ", " miss ", " miss. ", " ms ", " ms. ", " doctor", " dr ", " dr. ", " professor", " driving examiner", " teacher", " detective", " police officer", " nurse", " receptionist", " solicitor", " landlord", " manager")) return new(AgeBands.Adult, 0.88m);
|
||||||
if (ContainsAny(text, "elderly", "senior", "old woman", "old man")) return new(AgeBands.Senior, 0.9m);
|
if (ContainsAny(text, "elderly", "senior", "old woman", "old man")) return new(AgeBands.Senior, 0.9m);
|
||||||
return EvidenceSignal.Unknown;
|
return EvidenceSignal.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static EvidenceSignal EntityIdentityAgeBand(string name, string identityText)
|
||||||
|
{
|
||||||
|
var cleanName = (name ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
|
if (ContainsAny(cleanName, "mother", " mum", "father", " dad", "parent", "aunt", "uncle", "grandmother", "grandfather"))
|
||||||
|
{
|
||||||
|
return EvidenceSignal.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExplicitAgeBand(identityText);
|
||||||
|
}
|
||||||
|
|
||||||
private static EvidenceSignal RelationshipAgeBand(string name, string text)
|
private static EvidenceSignal RelationshipAgeBand(string name, string text)
|
||||||
{
|
{
|
||||||
if (ContainsAny(text, "toddler", "baby", "newborn")) return new(AgeBands.Child, 0.96m);
|
if (ContainsAny(text, "toddler", "baby", "newborn")) return new(AgeBands.Child, 0.96m);
|
||||||
var identity = $"{name} {text}";
|
var identity = $"{name} {text}";
|
||||||
if (ContainsAny(identity, "mother", " mum", "father", " dad", "parent", "aunt", "uncle", "teacher")
|
if (ContainsAny(identity, "mother", " mum", "father", " dad", "parent", "aunt", "uncle")
|
||||||
&& !PossessiveOfOtherIdentity(name, text))
|
&& !PossessiveOfOtherIdentity(name, text))
|
||||||
{
|
{
|
||||||
return new(AgeBands.MatureAdult, 0.74m);
|
return new(AgeBands.MatureAdult, 0.82m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContainsAny(identity, "teacher", "driving examiner", "detective", "police officer", "nurse", "receptionist", "solicitor", "landlord", "manager", "professor", "doctor")
|
||||||
|
&& !PossessiveOfOtherIdentity(name, text))
|
||||||
|
{
|
||||||
|
return new(AgeBands.Adult, 0.82m);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContainsAny(text, "grandmother", "grandfather")) return new(AgeBands.Senior, 0.82m);
|
if (ContainsAny(text, "grandmother", "grandfather")) return new(AgeBands.Senior, 0.82m);
|
||||||
@ -351,8 +379,7 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
|
|
||||||
private static EvidenceSignal NameAgeBand(string name)
|
private static EvidenceSignal NameAgeBand(string name)
|
||||||
{
|
{
|
||||||
var clean = name.Trim().ToLowerInvariant();
|
return EvidenceSignal.Unknown;
|
||||||
return clean is "beth" ? new(AgeBands.YoungTeen, 0.86m) : EvidenceSignal.Unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EvidenceSignal TitlePresentation(string text)
|
private static EvidenceSignal TitlePresentation(string text)
|
||||||
@ -378,8 +405,8 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
|
|
||||||
private static EvidenceSignal NamePresentation(string text)
|
private static EvidenceSignal NamePresentation(string text)
|
||||||
{
|
{
|
||||||
if (ContainsAny(text, " beth ", " maggie ", " rosie ", " grace ", " annie ", " helen ", " elen ")) return new(Presentations.Feminine, 0.82m);
|
if (ContainsAny(text, " beth ", " maggie ", " rosie ", " grace ", " rebecca ", " annie ", " mary ", " susan ", " helen ", " elen ")) return new(Presentations.Feminine, 0.82m);
|
||||||
if (ContainsAny(text, " graham ", " george ", " john ", " david ", " simon ", " kevin ", " colin ")) return new(Presentations.Masculine, 0.82m);
|
if (ContainsAny(text, " adam ", " colin ", " david ", " kevin ", " simon ", " graham ", " gareth ", " john ", " michael ", " peter ", " george ")) return new(Presentations.Masculine, 0.82m);
|
||||||
return EvidenceSignal.Unknown;
|
return EvidenceSignal.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,13 +442,13 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (StartsWithAny(clean, "mr", "father", "dad", "uncle", "brother", "son", "boy", "man", "male")
|
if (StartsWithAny(clean, "mr", "father", "dad", "uncle", "brother", "son", "boy", "man", "male")
|
||||||
|| clean is "graham" or "george" or "john" or "david" or "simon" or "kevin" or "colin")
|
|| clean is "adam" or "colin" or "david" or "kevin" or "simon" or "graham" or "gareth" or "john" or "michael" or "peter" or "george")
|
||||||
{
|
{
|
||||||
return new(Presentations.Masculine, 0.98m);
|
return new(Presentations.Masculine, 0.98m);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StartsWithAny(clean, "mrs", "miss", "ms", "mother", "mum", "mom", "aunt", "sister", "daughter", "girl", "woman", "female")
|
if (StartsWithAny(clean, "mrs", "miss", "ms", "mother", "mum", "mom", "aunt", "sister", "daughter", "girl", "woman", "female")
|
||||||
|| clean is "beth" or "maggie" or "rosie" or "grace" or "annie" or "helen" or "elen")
|
|| clean is "beth" or "maggie" or "grace" or "rosie" or "rebecca" or "annie" or "mary" or "susan" or "helen" or "elen")
|
||||||
{
|
{
|
||||||
return new(Presentations.Feminine, 0.98m);
|
return new(Presentations.Feminine, 0.98m);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -443,6 +443,14 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
candidate.Metadata.Profile,
|
candidate.Metadata.Profile,
|
||||||
isNamedCharacter: !IsUnresolvedName(observation.Name),
|
isNamedCharacter: !IsUnresolvedName(observation.Name),
|
||||||
alreadyAssignedToAnotherSignificantCharacter: alreadyAssigned && observation.IsSignificant));
|
alreadyAssignedToAnotherSignificantCharacter: alreadyAssigned && observation.IsSignificant));
|
||||||
|
if (observation.Evidence.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Unknown
|
||||||
|
&& candidate.Metadata.AgeBand is StoryIntelligenceIllustrationCompatibility.AgeBands.Child
|
||||||
|
or StoryIntelligenceIllustrationCompatibility.AgeBands.YoungTeen
|
||||||
|
or StoryIntelligenceIllustrationCompatibility.AgeBands.OlderTeen)
|
||||||
|
{
|
||||||
|
rejected.Add("unknown age should use an adult-compatible illustration until explicit child or teen evidence appears");
|
||||||
|
}
|
||||||
|
|
||||||
AddScore(observation.Evidence.AgeBand, candidate.Metadata.AgeBand, 25, "age band");
|
AddScore(observation.Evidence.AgeBand, candidate.Metadata.AgeBand, 25, "age band");
|
||||||
AddScore(observation.Evidence.Presentation, candidate.Metadata.Presentation, observation.Evidence.HasStrongPresentationEvidence ? 42 : 25, "presentation");
|
AddScore(observation.Evidence.Presentation, candidate.Metadata.Presentation, observation.Evidence.HasStrongPresentationEvidence ? 42 : 25, "presentation");
|
||||||
AddScore(observation.Evidence.HairColour, candidate.Metadata.HairColour, 20, "hair colour");
|
AddScore(observation.Evidence.HairColour, candidate.Metadata.HairColour, 20, "hair colour");
|
||||||
|
|||||||
@ -130,6 +130,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
{
|
{
|
||||||
Mode = "real",
|
Mode = "real",
|
||||||
RunId = run.StoryIntelligenceRunID,
|
RunId = run.StoryIntelligenceRunID,
|
||||||
|
BookId = run.BookID,
|
||||||
RunStatus = run.Status,
|
RunStatus = run.Status,
|
||||||
GeneratedUtc = DateTime.UtcNow,
|
GeneratedUtc = DateTime.UtcNow,
|
||||||
ChangeToken = ChangeToken(run, results),
|
ChangeToken = ChangeToken(run, results),
|
||||||
@ -208,11 +209,11 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
? "All known chapter runs completed successfully"
|
? "All known chapter runs completed successfully"
|
||||||
: "Weighted chapter-run progress";
|
: "Weighted chapter-run progress";
|
||||||
var overallProgressPercent = authoritativeImportProgress;
|
var overallProgressPercent = authoritativeImportProgress;
|
||||||
var overallElapsed = SessionElapsed(runSnapshots);
|
var overallElapsedMs = SessionElapsedMilliseconds(runSnapshots, isTerminal);
|
||||||
var overallRemaining = isSuccessfullyComplete ? "Ready to review" : isTerminal ? "Ready to review" : EstimateSessionRemaining(runSnapshots, parsedScenes, totalScenes);
|
var overallElapsed = FormatDuration(overallElapsedMs);
|
||||||
|
var overallRemaining = isSuccessfullyComplete ? "Ready to review" : isTerminal ? "Ready to review" : EstimateSessionRemaining(overallElapsedMs, parsedScenes, totalScenes);
|
||||||
var overallStage = isSuccessfullyComplete ? "Analysis Complete" : isTerminal ? "Analysis stopped" : BackendStageDisplay(activeSnapshot.Run);
|
var overallStage = isSuccessfullyComplete ? "Analysis Complete" : isTerminal ? "Analysis stopped" : BackendStageDisplay(activeSnapshot.Run);
|
||||||
var scenes = new List<StoryIntelligenceExperienceSceneState>();
|
var scenes = new List<StoryIntelligenceExperienceSceneState>();
|
||||||
var nextSceneNumber = 1;
|
|
||||||
var storyMemory = new StoryMemory();
|
var storyMemory = new StoryMemory();
|
||||||
var allCompleted = runSnapshots.SelectMany(snapshot => snapshot.Completed).ToList();
|
var allCompleted = runSnapshots.SelectMany(snapshot => snapshot.Completed).ToList();
|
||||||
var visibleSceneIds = VisibleSceneResultIds(allCompleted, current, SceneWindowPrevious, SceneWindowFollowing);
|
var visibleSceneIds = VisibleSceneResultIds(allCompleted, current, SceneWindowPrevious, SceneWindowFollowing);
|
||||||
@ -223,7 +224,6 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
snapshot.Parsed,
|
snapshot.Parsed,
|
||||||
snapshot.Completed,
|
snapshot.Completed,
|
||||||
current,
|
current,
|
||||||
continuousSceneNumberStart: nextSceneNumber,
|
|
||||||
overallCompletedScenes: parsedScenes,
|
overallCompletedScenes: parsedScenes,
|
||||||
overallTotalScenes: totalScenes,
|
overallTotalScenes: totalScenes,
|
||||||
overallChaptersAnalysed: completedChapterRuns,
|
overallChaptersAnalysed: completedChapterRuns,
|
||||||
@ -232,13 +232,11 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
overallStage: overallStage,
|
overallStage: overallStage,
|
||||||
overallElapsed: overallElapsed,
|
overallElapsed: overallElapsed,
|
||||||
overallRemaining: overallRemaining,
|
overallRemaining: overallRemaining,
|
||||||
continuousSceneNumbering: true,
|
|
||||||
overallTerminal: isTerminal,
|
overallTerminal: isTerminal,
|
||||||
storyMemory: storyMemory,
|
storyMemory: storyMemory,
|
||||||
includedSceneResultIds: visibleSceneIds)
|
includedSceneResultIds: visibleSceneIds)
|
||||||
.ToList();
|
.ToList();
|
||||||
scenes.AddRange(runScenes);
|
scenes.AddRange(runScenes);
|
||||||
nextSceneNumber += snapshot.Completed.Count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenes.Count == 0)
|
if (scenes.Count == 0)
|
||||||
@ -251,6 +249,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
Mode = "real",
|
Mode = "real",
|
||||||
ImportSessionId = session.StoryIntelligenceBookPipelineID,
|
ImportSessionId = session.StoryIntelligenceBookPipelineID,
|
||||||
RunId = activeSnapshot.Run.StoryIntelligenceRunID,
|
RunId = activeSnapshot.Run.StoryIntelligenceRunID,
|
||||||
|
BookId = session.BookID,
|
||||||
RunStatus = SessionStatus(session, runSnapshots),
|
RunStatus = SessionStatus(session, runSnapshots),
|
||||||
GeneratedUtc = DateTime.UtcNow,
|
GeneratedUtc = DateTime.UtcNow,
|
||||||
ChangeToken = SessionChangeToken(session, runSnapshots),
|
ChangeToken = SessionChangeToken(session, runSnapshots),
|
||||||
@ -837,19 +836,18 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string EstimateSessionRemaining(IReadOnlyList<RunSnapshot> snapshots, int completedScenes, int totalScenes)
|
private static string EstimateSessionRemaining(long elapsedMs, int completedScenes, int totalScenes)
|
||||||
{
|
{
|
||||||
if (totalScenes <= 0 || completedScenes <= 0)
|
if (totalScenes <= 0 || completedScenes <= 0)
|
||||||
{
|
{
|
||||||
return snapshots.All(snapshot => IsTerminal(snapshot.Run.Status)) ? "Ready to review" : "Calculating";
|
return "Calculating";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completedScenes >= totalScenes && snapshots.All(snapshot => IsTerminal(snapshot.Run.Status)))
|
if (completedScenes >= totalScenes)
|
||||||
{
|
{
|
||||||
return "Ready to review";
|
return "Ready to review";
|
||||||
}
|
}
|
||||||
|
|
||||||
var elapsedMs = snapshots.Sum(snapshot => snapshot.Run.TotalDurationMs ?? Math.Max(0, Convert.ToInt64((DateTime.UtcNow - snapshot.Run.StartedUtc).TotalMilliseconds)));
|
|
||||||
var averageMs = elapsedMs / Math.Max(1, completedScenes);
|
var averageMs = elapsedMs / Math.Max(1, completedScenes);
|
||||||
return FormatDuration(averageMs * Math.Max(0, totalScenes - completedScenes));
|
return FormatDuration(averageMs * Math.Max(0, totalScenes - completedScenes));
|
||||||
}
|
}
|
||||||
@ -1798,11 +1796,21 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
return FormatDuration(elapsedMs);
|
return FormatDuration(elapsedMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SessionElapsed(IReadOnlyList<RunSnapshot> snapshots)
|
private static long SessionElapsedMilliseconds(IReadOnlyList<RunSnapshot> snapshots, bool isTerminal)
|
||||||
{
|
{
|
||||||
var elapsedMs = snapshots.Sum(snapshot => snapshot.Run.TotalDurationMs
|
if (snapshots.Count == 0)
|
||||||
?? Math.Max(0, Convert.ToInt64((DateTime.UtcNow - snapshot.Run.StartedUtc).TotalMilliseconds)));
|
{
|
||||||
return FormatDuration(elapsedMs);
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var started = snapshots.Min(snapshot => snapshot.Run.StartedUtc);
|
||||||
|
var ended = isTerminal
|
||||||
|
? snapshots
|
||||||
|
.Select(snapshot => snapshot.Run.CompletedUtc ?? snapshot.Run.UpdatedUtc)
|
||||||
|
.DefaultIfEmpty(DateTime.UtcNow)
|
||||||
|
.Max()
|
||||||
|
: DateTime.UtcNow;
|
||||||
|
return Math.Max(0, Convert.ToInt64((ended - started).TotalMilliseconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatDuration(long milliseconds)
|
private static string FormatDuration(long milliseconds)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ public sealed class StoryIntelligenceExperiencePrototypeViewModel
|
|||||||
public string Mode { get; init; } = "simulation";
|
public string Mode { get; init; } = "simulation";
|
||||||
public int? RunId { get; init; }
|
public int? RunId { get; init; }
|
||||||
public int? ImportSessionId { get; init; }
|
public int? ImportSessionId { get; init; }
|
||||||
|
public int? BookId { get; init; }
|
||||||
public string? RunStatus { get; init; }
|
public string? RunStatus { get; init; }
|
||||||
public string? ChangeToken { get; init; }
|
public string? ChangeToken { get; init; }
|
||||||
public DateTime GeneratedUtc { get; init; } = DateTime.UtcNow;
|
public DateTime GeneratedUtc { get; init; } = DateTime.UtcNow;
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
Mode = Model.Mode,
|
Mode = Model.Mode,
|
||||||
RunId = Model.RunId,
|
RunId = Model.RunId,
|
||||||
ImportSessionId = Model.ImportSessionId,
|
ImportSessionId = Model.ImportSessionId,
|
||||||
|
BookId = Model.BookId,
|
||||||
RunStatus = Model.RunStatus,
|
RunStatus = Model.RunStatus,
|
||||||
GeneratedUtc = Model.GeneratedUtc,
|
GeneratedUtc = Model.GeneratedUtc,
|
||||||
IsTerminal = false,
|
IsTerminal = false,
|
||||||
@ -50,6 +51,7 @@
|
|||||||
data-image-diagnostics="true"
|
data-image-diagnostics="true"
|
||||||
data-mode="@Model.Mode"
|
data-mode="@Model.Mode"
|
||||||
data-run-id="@Model.RunId"
|
data-run-id="@Model.RunId"
|
||||||
|
data-book-id="@Model.BookId"
|
||||||
data-import-session-id="@Model.ImportSessionId"
|
data-import-session-id="@Model.ImportSessionId"
|
||||||
data-snapshot-url="@(Model.ImportSessionId.HasValue ? $"/api/story-intelligence/import-sessions/{Model.ImportSessionId.Value}/visualisation-snapshot" : Model.RunId.HasValue ? $"/api/story-intelligence/runs/{Model.RunId.Value}/visualisation-snapshot" : string.Empty)"
|
data-snapshot-url="@(Model.ImportSessionId.HasValue ? $"/api/story-intelligence/import-sessions/{Model.ImportSessionId.Value}/visualisation-snapshot" : Model.RunId.HasValue ? $"/api/story-intelligence/runs/{Model.RunId.Value}/visualisation-snapshot" : string.Empty)"
|
||||||
data-change-token="@(Model.Mode == "real" ? string.Empty : Model.ChangeToken)"
|
data-change-token="@(Model.Mode == "real" ? string.Empty : Model.ChangeToken)"
|
||||||
@ -66,7 +68,14 @@
|
|||||||
<span data-stage-label>Preparing</span>
|
<span data-stage-label>Preparing</span>
|
||||||
<span>Scene <strong data-current-scene>1</strong></span>
|
<span>Scene <strong data-current-scene>1</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
@if (Model.Mode == "real" && Model.BookId.HasValue)
|
||||||
|
{
|
||||||
|
<a class="story-exp-background" href="/onboarding/story-intelligence/book/@Model.BookId.Value/continue">Return to Import</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<a class="story-exp-background" href="/Development/StoryIntelligenceExperience?mode=simulation">Simulation</a>
|
<a class="story-exp-background" href="/Development/StoryIntelligenceExperience?mode=simulation">Simulation</a>
|
||||||
|
}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@if (Model.AvailableRuns.Count > 0)
|
@if (Model.AvailableRuns.Count > 0)
|
||||||
@ -181,6 +190,8 @@
|
|||||||
</aside>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@if (Model.Mode == "simulation")
|
||||||
|
{
|
||||||
<aside class="story-exp-controls" aria-label="Prototype controls">
|
<aside class="story-exp-controls" aria-label="Prototype controls">
|
||||||
<div>
|
<div>
|
||||||
<span>Prototype controls</span>
|
<span>Prototype controls</span>
|
||||||
@ -195,6 +206,7 @@
|
|||||||
<input type="range" min="3500" max="10000" step="500" value="6200" data-speed-control />
|
<input type="range" min="3500" max="10000" step="500" value="6200" data-speed-control />
|
||||||
</label>
|
</label>
|
||||||
</aside>
|
</aside>
|
||||||
|
}
|
||||||
|
|
||||||
<details class="story-exp-diagnostics" data-diagnostics>
|
<details class="story-exp-diagnostics" data-diagnostics>
|
||||||
<summary>Diagnostics</summary>
|
<summary>Diagnostics</summary>
|
||||||
|
|||||||
@ -288,10 +288,10 @@ input:focus-visible {
|
|||||||
|
|
||||||
.story-exp-shell {
|
.story-exp-shell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(206px, 0.54fr) minmax(760px, 2.44fr) minmax(214px, 0.56fr);
|
grid-template-columns: minmax(180px, 0.46fr) minmax(820px, 2.72fr) minmax(188px, 0.48fr);
|
||||||
gap: 16px;
|
gap: 12px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 14px 22px 74px;
|
padding: 12px 18px 58px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.story-exp-left,
|
.story-exp-left,
|
||||||
@ -565,7 +565,7 @@ input:focus-visible {
|
|||||||
.story-exp-connections {
|
.story-exp-connections {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 1;
|
z-index: 60;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@ -641,7 +641,7 @@ input:focus-visible {
|
|||||||
.story-exp-zone {
|
.story-exp-zone {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 2;
|
z-index: 20;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@ -683,13 +683,13 @@ input:focus-visible {
|
|||||||
height: var(--node-size, 150px);
|
height: var(--node-size, 150px);
|
||||||
transform: translate(var(--x, 0), var(--y, 0)) scale(var(--scale, 1));
|
transform: translate(var(--x, 0), var(--y, 0)) scale(var(--scale, 1));
|
||||||
opacity: var(--opacity, 1);
|
opacity: var(--opacity, 1);
|
||||||
z-index: var(--z, 3);
|
z-index: calc(var(--z, 3) + 30);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transition: transform 1050ms cubic-bezier(.16, .84, .28, 1), opacity 780ms ease;
|
transition: transform 1050ms cubic-bezier(.16, .84, .28, 1), opacity 780ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.story-character.is-pov {
|
.story-character.is-pov {
|
||||||
z-index: 7;
|
z-index: 48;
|
||||||
}
|
}
|
||||||
|
|
||||||
.story-character.is-context .story-character__image {
|
.story-character.is-context .story-character__image {
|
||||||
@ -699,6 +699,8 @@ input:focus-visible {
|
|||||||
.story-character__image,
|
.story-character__image,
|
||||||
.story-location__image,
|
.story-location__image,
|
||||||
.story-asset__image {
|
.story-asset__image {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid rgba(169, 205, 240, 0.36);
|
border: 1px solid rgba(169, 205, 240, 0.36);
|
||||||
@ -727,6 +729,7 @@ input:focus-visible {
|
|||||||
|
|
||||||
.story-character__text {
|
.story-character__text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 80;
|
||||||
top: calc(var(--node-size, 150px) + 9px);
|
top: calc(var(--node-size, 150px) + 9px);
|
||||||
left: 50%;
|
left: 50%;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -817,7 +820,7 @@ input:focus-visible {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
width: var(--node-size, 190px);
|
width: var(--node-size, 190px);
|
||||||
transform: translate(var(--x, 0), var(--y, 0));
|
transform: translate(var(--x, 0), var(--y, 0));
|
||||||
z-index: 4;
|
z-index: 42;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transition: opacity 560ms ease, transform 850ms cubic-bezier(.16, .84, .28, 1);
|
transition: opacity 560ms ease, transform 850ms cubic-bezier(.16, .84, .28, 1);
|
||||||
}
|
}
|
||||||
@ -841,6 +844,8 @@ input:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.story-location__text {
|
.story-location__text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 80;
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
@ -887,7 +892,7 @@ input:focus-visible {
|
|||||||
width: max(var(--node-size, 78px), 104px);
|
width: max(var(--node-size, 78px), 104px);
|
||||||
opacity: var(--opacity, 1);
|
opacity: var(--opacity, 1);
|
||||||
transform: translate(var(--x, 0), var(--y, 0)) scale(var(--scale, 1));
|
transform: translate(var(--x, 0), var(--y, 0)) scale(var(--scale, 1));
|
||||||
z-index: var(--z, 3);
|
z-index: calc(var(--z, 3) + 30);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transition: transform 860ms cubic-bezier(.16, .84, .28, 1), opacity 620ms ease;
|
transition: transform 860ms cubic-bezier(.16, .84, .28, 1), opacity 620ms ease;
|
||||||
}
|
}
|
||||||
@ -901,6 +906,8 @@ input:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.story-asset__text {
|
.story-asset__text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 80;
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|||||||
@ -1920,6 +1920,21 @@
|
|||||||
let scene = null;
|
let scene = null;
|
||||||
let totalWordCount = 0;
|
let totalWordCount = 0;
|
||||||
let detectedHeadingChapters = 0;
|
let detectedHeadingChapters = 0;
|
||||||
|
let manuscriptStarted = false;
|
||||||
|
|
||||||
|
const isFrontMatterHeading = (value) => {
|
||||||
|
const clean = normalizeTitleForResolve(value || "", "").toLocaleLowerCase();
|
||||||
|
return [
|
||||||
|
"title page",
|
||||||
|
"copyright",
|
||||||
|
"dedication",
|
||||||
|
"contents",
|
||||||
|
"table of contents",
|
||||||
|
"acknowledgements",
|
||||||
|
"acknowledgments",
|
||||||
|
"about the author"
|
||||||
|
].includes(clean);
|
||||||
|
};
|
||||||
|
|
||||||
const finishScene = () => {
|
const finishScene = () => {
|
||||||
scene = null;
|
scene = null;
|
||||||
@ -1982,10 +1997,15 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
documentText.push(text);
|
|
||||||
const words = countWords(text);
|
const words = countWords(text);
|
||||||
|
|
||||||
if (isBuiltInHeading(paragraph, 1)) {
|
if (isBuiltInHeading(paragraph, 1)) {
|
||||||
|
if (!manuscriptStarted && isFrontMatterHeading(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manuscriptStarted = true;
|
||||||
|
documentText.push(text);
|
||||||
detectedHeadingChapters += 1;
|
detectedHeadingChapters += 1;
|
||||||
startChapter(paragraph, text);
|
startChapter(paragraph, text);
|
||||||
totalWordCount += words;
|
totalWordCount += words;
|
||||||
@ -1995,9 +2015,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!chapter) {
|
if (!chapter) {
|
||||||
|
if (!manuscriptStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
startChapter(paragraph, "Opening pages", true);
|
startChapter(paragraph, "Opening pages", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
documentText.push(text);
|
||||||
|
|
||||||
if (isBuiltInHeading(paragraph, 2)) {
|
if (isBuiltInHeading(paragraph, 2)) {
|
||||||
startScene(paragraph, text);
|
startScene(paragraph, text);
|
||||||
totalWordCount += words;
|
totalWordCount += words;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user