people tags #387
This commit is contained in:
parent
14461c362f
commit
da4a0f7c14
@ -123,6 +123,37 @@ namespace RMuseum.DbContext
|
|||||||
.HasIndex(m => m.Name)
|
.HasIndex(m => m.Name)
|
||||||
.IsUnique();
|
.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>()
|
builder.Entity<GanjoorUserBookmark>()
|
||||||
.HasIndex(b => new { b.UserId, b.PoemId, b.CoupletIndex });
|
.HasIndex(b => new { b.UserId, b.PoemId, b.CoupletIndex });
|
||||||
|
|
||||||
@ -578,6 +609,16 @@ namespace RMuseum.DbContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<GanjoorRelatedPerson> GanjoorRelatedPersons { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Books (PDF Library)
|
/// Books (PDF Library)
|
||||||
/// </summary>
|
/// </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; }
|
public int? LunarDay { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public int? PersonId { get; set; }
|
public int? PersonId { get; set; }
|
||||||
|
|
||||||
@ -65,6 +66,35 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual GanjoorRelatedPerson Person { get; set; }
|
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>
|
/// <summary>
|
||||||
/// if true, this tag is excluded from category/poet-level map aggregation (e.g. a place mentioned only
|
/// 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
|
/// for comparison, not actually visited/relevant to the poet's own path) - default false, matching
|
||||||
|
|||||||
@ -69,5 +69,14 @@
|
|||||||
/// AI generated
|
/// AI generated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MachineGenerated { get; set; }
|
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