people tags #387
This commit is contained in:
parent
14461c362f
commit
da4a0f7c14
@ -123,6 +123,37 @@ namespace RMuseum.DbContext
|
||||
.HasIndex(m => m.Name)
|
||||
.IsUnique();
|
||||
|
||||
// GanjoorPersonRelation has two required FKs to the same table (GanjoorRelatedPerson) -
|
||||
// left at their EF Core default (Cascade, since both are required/non-nullable), SQL
|
||||
// Server refuses to create the second FK with "may cause cycles or multiple cascade
|
||||
// paths". Restricting one side (Person2) is enough to break the ambiguity; deleting a
|
||||
// person that's still referenced by a relation should be prevented at the application
|
||||
// level anyway (via a "still has family tree entries" check), not silently cascaded.
|
||||
builder.Entity<GanjoorPersonRelation>()
|
||||
.HasOne(r => r.Person1)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.Person1Id)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<GanjoorPersonRelation>()
|
||||
.HasOne(r => r.Person2)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.Person2Id)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// same two-required-FKs-to-the-same-table situation as GanjoorPersonRelation above
|
||||
builder.Entity<GanjoorPersonAffiliation>()
|
||||
.HasOne(a => a.Person1)
|
||||
.WithMany()
|
||||
.HasForeignKey(a => a.Person1Id)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<GanjoorPersonAffiliation>()
|
||||
.HasOne(a => a.Person2)
|
||||
.WithMany()
|
||||
.HasForeignKey(a => a.Person2Id)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<GanjoorUserBookmark>()
|
||||
.HasIndex(b => new { b.UserId, b.PoemId, b.CoupletIndex });
|
||||
|
||||
@ -578,6 +609,16 @@ namespace RMuseum.DbContext
|
||||
/// </summary>
|
||||
public DbSet<GanjoorRelatedPerson> GanjoorRelatedPersons { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// approved kinship edges between people (family tree) - see GanjoorPersonRelation
|
||||
/// </summary>
|
||||
public DbSet<GanjoorPersonRelation> GanjoorPersonRelations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// approved non-family ties between people (e.g. minister-to-king) - see GanjoorPersonAffiliation
|
||||
/// </summary>
|
||||
public DbSet<GanjoorPersonAffiliation> GanjoorPersonAffiliations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Books (PDF Library)
|
||||
/// </summary>
|
||||
|
||||
50
RMuseum/Models/Ganjoor/GanjoorPersonAffiliation.cs
Normal file
50
RMuseum/Models/Ganjoor/GanjoorPersonAffiliation.cs
Normal file
@ -0,0 +1,50 @@
|
||||
namespace RMuseum.Models.Ganjoor
|
||||
{
|
||||
/// <summary>
|
||||
/// an approved non-family tie between two people (GanjoorRelatedPerson rows) - e.g. a minister
|
||||
/// serving a king. Kept separate from GanjoorPersonRelation (kinship) rather than folded into
|
||||
/// it: this is what lets two unrelated family trees show up as adjacent to each other (e.g.
|
||||
/// browsing the Barmakid tree surfaces a link out to the Abbasid tree via a shared minister)
|
||||
/// without treating "family tree" as its own entity to be linked - the tie is between the two
|
||||
/// people, and the tree-to-tree adjacency is just what falls out of rendering it that way.
|
||||
/// </summary>
|
||||
public class GanjoorPersonAffiliation
|
||||
{
|
||||
/// <summary>
|
||||
/// record id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// first person in the tie - for a directional type (Minister, Advisor, Courtier, Patron)
|
||||
/// this is the one in the subordinate/serving role; for a symmetric one (Ally, Rival) order
|
||||
/// doesn't matter
|
||||
/// </summary>
|
||||
public int Person1Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// first person (navigation)
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// second person in the tie - for a directional type this is the one being served
|
||||
/// </summary>
|
||||
public int Person2Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// second person (navigation)
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the kind of tie between Person1 and Person2
|
||||
/// </summary>
|
||||
public PersonAffiliationType AffiliationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// free-text note (e.g. sourcing/reasoning, or what the tie actually is when AffiliationType is Other)
|
||||
/// </summary>
|
||||
public string Note { get; set; }
|
||||
}
|
||||
}
|
||||
55
RMuseum/Models/Ganjoor/GanjoorPersonRelation.cs
Normal file
55
RMuseum/Models/Ganjoor/GanjoorPersonRelation.cs
Normal file
@ -0,0 +1,55 @@
|
||||
namespace RMuseum.Models.Ganjoor
|
||||
{
|
||||
/// <summary>
|
||||
/// an approved kinship edge between two people (GanjoorRelatedPerson rows) - the live/materialized
|
||||
/// counterpart of a pending suggestion, which travels as JSON on GanjoorPoemGeoDateTagCorrection's
|
||||
/// SuggestedPersonGraphJson until it's approved and turned into rows here. The graph as a whole
|
||||
/// (all rows in this table) is a general kinship graph, not a strict tree - see PersonRelationType.
|
||||
/// </summary>
|
||||
public class GanjoorPersonRelation
|
||||
{
|
||||
/// <summary>
|
||||
/// record id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// first person in the relation - for a directional relation type (Parent, Ancestor) this is
|
||||
/// the parent/ancestor side; for a symmetric one (Sibling, Spouse) order doesn't matter
|
||||
/// </summary>
|
||||
public int Person1Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// first person (navigation)
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// second person in the relation - for a directional relation type (Parent, Ancestor) this is
|
||||
/// the child/descendant side
|
||||
/// </summary>
|
||||
public int Person2Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// second person (navigation)
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the kind of relation between Person1 and Person2
|
||||
/// </summary>
|
||||
public PersonRelationType RelationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// for RelationType == Ancestor, an optional known exact degree (e.g. 2 for "grandparent",
|
||||
/// 3 for "great-grandparent") - left null when only the relative order is known, not the
|
||||
/// exact number of generations in between. Not used for other relation types.
|
||||
/// </summary>
|
||||
public int? DegreeHint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// free-text note (e.g. sourcing/reasoning for this relation)
|
||||
/// </summary>
|
||||
public string Note { get; set; }
|
||||
}
|
||||
}
|
||||
@ -56,7 +56,8 @@
|
||||
public int? LunarDay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// related person id (existing, approved GanjoorRelatedPerson only - no suggestion path for new people yet)
|
||||
/// related person id - an existing, already approved GanjoorRelatedPerson. Set this OR
|
||||
/// SuggestedPersonGraphJson below, not both (same pattern as LocationId/Suggested* above).
|
||||
/// </summary>
|
||||
public int? PersonId { get; set; }
|
||||
|
||||
@ -65,6 +66,35 @@
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// a brand new, not yet approved person (and optionally that person's relatives/relations,
|
||||
/// which may themselves be new people) - serialized JSON rather than its own set of
|
||||
/// correction tables, because a single suggestion can introduce several interlinked new
|
||||
/// people at once (e.g. "add this person, and their father, and the relation between them")
|
||||
/// and a new person referencing another not-yet-existing new person has no real id to point
|
||||
/// at until the whole graph is approved together. Expected shape (local keys are only used
|
||||
/// to resolve relations within this same submission and never stored beyond approval time):
|
||||
/// {
|
||||
/// "person": { "localKey": "p1", "existingPersonId": null, "name": "...", "description": "...",
|
||||
/// "wikiUrl": "...", "birthYearInLHijri": null, "deathYearInLHijri": null,
|
||||
/// "validBirthDate": false, "validDeathDate": false,
|
||||
/// "birthLocationId": null, "deathLocationId": null,
|
||||
/// "familyTreeCaption": null },
|
||||
/// "relatedPeople": [ { "localKey": "p2", "existingPersonId": 42, ... } ],
|
||||
/// "relations": [ { "kind": "family", "person1": "p1", "person2": "p2", "relationType": "Parent",
|
||||
/// "degreeHint": null, "note": "..." },
|
||||
/// { "kind": "affiliation", "person1": "p2", "person2": "p3",
|
||||
/// "affiliationType": "Minister", "note": "..." } ]
|
||||
/// }
|
||||
/// "person" is the node that ends up assigned to PersonId once approved. Only set when
|
||||
/// PersonId above is null. Each entry in "relations" carries a "kind" discriminator so one
|
||||
/// submission can suggest both kinship edges (materialized as GanjoorPersonRelation,
|
||||
/// "relationType" against PersonRelationType) and non-family ties (materialized as
|
||||
/// GanjoorPersonAffiliation, "affiliationType" against PersonAffiliationType) at once - e.g.
|
||||
/// introducing a person along with both their father and the king they served.
|
||||
/// </summary>
|
||||
public string SuggestedPersonGraphJson { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// if true, this tag is excluded from category/poet-level map aggregation (e.g. a place mentioned only
|
||||
/// for comparison, not actually visited/relevant to the poet's own path) - default false, matching
|
||||
|
||||
@ -69,5 +69,14 @@
|
||||
/// AI generated
|
||||
/// </summary>
|
||||
public bool MachineGenerated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// optional caption for the family tree this person is treated as the root of (e.g.
|
||||
/// "ساسانیان", "آل برمک") - purely a display label for whoever a tree is being browsed
|
||||
/// from; nothing enforces that this person actually has no recorded ancestors themselves,
|
||||
/// and most people will leave this null (only whichever person a tree is "named after"
|
||||
/// needs one set).
|
||||
/// </summary>
|
||||
public string FamilyTreeCaption { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
48
RMuseum/Models/Ganjoor/PersonAffiliationType.cs
Normal file
48
RMuseum/Models/Ganjoor/PersonAffiliationType.cs
Normal file
@ -0,0 +1,48 @@
|
||||
namespace RMuseum.Models.Ganjoor
|
||||
{
|
||||
/// <summary>
|
||||
/// the kind of non-family tie a GanjoorPersonAffiliation represents between two people - e.g.
|
||||
/// a minister serving a king, an advisor, a patron. Unlike PersonRelationType this has nothing
|
||||
/// to do with kinship; it's what lets two otherwise unrelated family trees show up as adjacent
|
||||
/// (e.g. "some Barmakids served the Abbasid court") without merging them into one tree. New
|
||||
/// values can be appended safely later (stored as int) as more cases come up.
|
||||
/// </summary>
|
||||
public enum PersonAffiliationType
|
||||
{
|
||||
/// <summary>
|
||||
/// Person1 served as minister/vizier to Person2
|
||||
/// </summary>
|
||||
Minister = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 was an advisor/counselor to Person2, without holding a formal ministerial post
|
||||
/// </summary>
|
||||
Advisor = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 was a courtier/attendant/servant of Person2 (a catch-all for court-affiliated
|
||||
/// roles not covered by a more specific type)
|
||||
/// </summary>
|
||||
Courtier = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 was a patron/sponsor of Person2 (e.g. a king patronizing a poet)
|
||||
/// </summary>
|
||||
Patron = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 and Person2 were allies (symmetric - order doesn't matter)
|
||||
/// </summary>
|
||||
Ally = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 and Person2 were rivals/enemies (symmetric - order doesn't matter)
|
||||
/// </summary>
|
||||
Rival = 5,
|
||||
|
||||
/// <summary>
|
||||
/// doesn't fit any of the above - rely on Note for what the tie actually is
|
||||
/// </summary>
|
||||
Other = 99,
|
||||
}
|
||||
}
|
||||
35
RMuseum/Models/Ganjoor/PersonRelationType.cs
Normal file
35
RMuseum/Models/Ganjoor/PersonRelationType.cs
Normal file
@ -0,0 +1,35 @@
|
||||
namespace RMuseum.Models.Ganjoor
|
||||
{
|
||||
/// <summary>
|
||||
/// the kind of kinship edge a GanjoorPersonRelation represents between two GanjoorRelatedPerson
|
||||
/// rows. The graph is not a strict tree - siblings can be known without their parents, an
|
||||
/// ancestor can be known without the exact number of generations in between, etc. - so this is
|
||||
/// a small, deliberately open set rather than a rigid parent/child-only model.
|
||||
/// </summary>
|
||||
public enum PersonRelationType
|
||||
{
|
||||
/// <summary>
|
||||
/// Person1 is a parent of Person2 (directional - the mirror "child" relation is implied,
|
||||
/// not stored as a second row)
|
||||
/// </summary>
|
||||
Parent = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 and Person2 are siblings (symmetric - order doesn't matter). Doesn't require
|
||||
/// either parent to be known/recorded.
|
||||
/// </summary>
|
||||
Sibling = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 and Person2 are spouses (symmetric - order doesn't matter)
|
||||
/// </summary>
|
||||
Spouse = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Person1 is a known ancestor of Person2 (directional) but the exact number of generations
|
||||
/// between them is not known/recorded - see GanjoorPersonRelation.DegreeHint for the case
|
||||
/// where the exact degree (e.g. "grandparent") IS known
|
||||
/// </summary>
|
||||
Ancestor = 3,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user