Nhật ký công việc cho nhiệm vụ "Xem xét lại cấu trúc trang web, cải tiến nó và xây dựng lại nội dung"

13 июл. 2026 г., 19:09:09

Thêm thông tin từ ChatGPT

Vâng, với mô hình này, phía kỹ thuật không phải là điểm hạn chế. Hơn nữa, bạn đã có sẵn hầu như mọi thứ cần thiết cho một knowledge graph du lịch — chỉ là hiện tại nó đang được mô tả bằng các thuật ngữ tổng quát.

Quan sát chính: không cần phải tạo các bảng riêng cho từng thực thể du lịch. Các tỉnh, thành phố, địa điểm, hoạt động, nhu cầu, kịch bản và khu phức hợp đều có thể là KBConcept, và ý nghĩa mối quan hệ giữa chúng là KBFact với các bên tham gia thông qua KBFactParticipation. Chính vì điều này mà mô hình n-ary của bạn tỏ ra cực kỳ phù hợp.

Cách tôi đọc kiến trúc của bạn

Bạn đã có sẵn năm tầng rất mạnh mẽ.

1. Các thực thể

KBConcept là một nút ngữ nghĩa phổ quát:

  • Việt Nam;
  • Tỉnh Khánh Hòa;
  • Nha Trang;
  • VinWonders;
  • thác nước;
  • lặn biển;
  • nghỉ dưỡng gia đình;
  • thời tiết mưa;
  • chuyến đi ba ngày;
  • hành trình cụ thể.

Đồng thời nó có:

  • type;
  • phân cấp thông qua parentId;
  • gốc thông qua rootId;
  • tọa độ;
  • dữ liệu tùy ý trong data;
  • tên đa ngôn ngữ qua KBLabel;
  • tệp và hình ảnh;
  • URL riêng qua SiteRoute.

Tức là đây đã là nền tảng sẵn sàng cho cả danh mục (catalog) và đồ thị (graph) cùng một lúc.

2. Các mối quan hệ và khẳng định (Assertions)

KBFact không chỉ là một cạnh của đồ thị, mà là một khẳng định hoàn chỉnh:

  • loại quan hệ;
  • biểu diễn văn bản;
  • tính hiệu lực theo thời gian;
  • nguồn gốc;
  • độ tin cậy;
  • trạng thái kiểm tra;
  • mức độ quan trọng;
  • nguồn gốc của sự thật (provenance).

KBFactParticipation cho phép một sự thật liên kết bất kỳ số lượng đối tượng nào với các vai trò khác nhau.

Ví dụ, khẳng định:

VinWonders phù hợp cho gia đình có trẻ em để nghỉ dưỡng trong hai ngày thời tiết khô ráo.

Có thể được thể hiện bằng một sự thật duy nhất:

KBFact.type = "visit_suitability"

Những người tham gia:

VinWonders       role=destination
Gia đình có trẻ em role=audience
Hai ngày         role=recommended_duration
Thời tiết khô ráo role=preferred_condition

Điều này đã biểu cảm hơn rất nhiều so với bảng place_tags thông thường.

3. Tính tạm thời và sự bất định

Đối với du lịch, điều này cực kỳ quan trọng.

Trong mô hình của bạn, một sự thật có thể có:

  • validFrom;
  • validTo;
  • knownSince;
  • confidence;
  • status;
  • source.

Nghĩa là bạn có thể lưu trữ bình thường:

  • đóng cửa theo mùa;
  • sửa chữa tạm thời;
  • thay đổi giá;
  • lịch trình mới;
  • đường đi xuống cấp;
  • mùa sứa;
  • đóng cửa cáp treo;
  • lễ hội;
  • hạn chế tắm biển tạm thời.

Hơn nữa, tri thức mới không bắt buộc phải ghi đè lên tri thức cũ. Điều này hoàn toàn phù hợp với thông tin du lịch thực tế.

4. Các mâu thuẫn (Conflicts)

KBConflictKBConstraint cho phép bạn không phải giả vờ rằng cơ sở dữ liệu luôn biết sự thật tuyệt đối.

Ví dụ:

  • trang web chính thức nói vé vào cửa là 500.000 VNĐ;
  • đánh giá mới nhất cho biết giá là 600.000 VNĐ;
  • trang tổng hợp hiển thị 550.000 VNĐ.

Thay vì chọn ngẫu nhiên, bạn có thể lưu trữ cả ba sự thật và mở một xung đột value_mismatch.

Đối với nút "Cần biết gì lúc này", điều này cực kỳ có giá trị:

Giá chính thức là 500.000 ₫, nhưng hai nguồn gần đây cho biết giá là 600.000 ₫. Chúng tôi khuyên bạn nên kiểm tra trước khi đi.

Điều này trung thực hơn rất nhiều so với cơ sở dữ liệu thông thường.

5. Không gian tri thức theo ngữ cảnh (Knowledge Spaces)

KBKnowledgeSpaceKBFactProjection cho phép cùng một sự thật được diễn giải khác nhau trong các ngữ cảnh khác nhau.

Ví dụ:

  • cơ sở biên tập của VietnamGuru;
  • không gian cá nhân của người dùng;
  • kết quả tìm kiếm web tạm thời;
  • dữ liệu từ chuyên gia địa phương;
  • tin nhắn của người dùng;
  • không gian của một hành trình cụ thể.

Một sự thật có thể có các mức độ khác nhau về:

  • khả năng hiển thị;
  • độ đáng tin cậy;
  • tầm quan trọng.

Điều này giúp không trộn lẫn dữ liệu được biên tập viên xác thực với các mẹo của người dùng.


Cách chuyển đổi du lịch sang mô hình này

Tôi sẽ không tạo hàng chục mô hình Prisma như Province, City, Place, Activity, Need, Scenario.

Thay vào đó là một từ vựng giới hạn cho các loại KBConcept.type.

Ví dụ:

geo_country
geo_province
geo_city
geo_district

place
place_complex
place_component
transport_hub

place_type
activity
service
need
interest
avoidance
audience
condition
season
time_window
duration
scenario

route
route_segment
route_day
trip

Điều này không nhất thiết phải biến thành enum ngay trong Prisma. Ở giai đoạn đầu, kiểu chuỗi type thậm chí còn hữu ích hơn — nó cho phép phát triển hệ thống ngữ nghĩa (ontology) mà không cần migration schema.

Tuy nhiên, bản thân từ vựng các loại nên được tập trung hóa trong mã nguồn.


Phân cấp địa lý

Đối với cấu trúc hành chính, chỉ cần dùng parentId:

Việt Nam
└── Khánh Hòa
    ├── Nha Trang
    └── Cam Ranh

Nhưng parentId chỉ nên được sử dụng cho phân cấp thực sự.

Không nên dùng nó để thể hiện:

  • địa điểm nằm gần đó;
  • địa điểm nằm trong hành trình;
  • nhà hàng nằm trong khu phức hợp;
  • thành phố là căn cứ để đi du lịch.

Đối với những trường hợp này, bạn cần các sự thật (facts).

Ví dụ:

Fact.type = "located_in"
place       role=subject
city        role=container

Tại sao không giới hạn ở parentId? Bởi vì một đối tượng có thể đồng thời nằm ở:

  • trong quận;
  • trong thành phố;
  • trong tỉnh;
  • trong khu du lịch;
  • trong khuôn viên khu phức hợp.

Đây không còn là cây (tree) nữa, mà là đồ thị (graph).


Cách thể hiện các loại địa điểm

"Loại địa điểm" hiện tại của bạn là một KBConcept riêng biệt:

Thác nước
Hang động
Hang hốc (Grotto)
Chùa
Sòng bạc
Nhà hàng
Trung tâm lặn biển

Mối liên kết:

Fact.type = "has_type"

place      role=subject
placeType  role=type

Một đối tượng có thể có nhiều loại.

Ví dụ, một khu nghỉ dưỡng:

Khu sinh thái
Khu cắm trại
Quần thể thiên nhiên
Khu vực tắm biển
Tổ hợp nhà hàng

Cách này tốt hơn trường typeId, vì các đối tượng phức hợp hầu như không bao giờ vừa vặn trong một danh mục duy nhất.


Cách thể hiện mối quan hệ ngữ nghĩa giữa các loại

Ví dụ của bạn:

Nếu một người không thích hang động, họ không cần hang hốc (grotto).

Cần liên kết chính các loại với nhau.

Fact.type = "semantic_subtype"

Hang hốc  role=child
Hang động role=parent

Bạn có thể xây dựng một hệ thống ngữ nghĩa như sau:

Các đối tượng tự nhiên ngầm dưới đất
├── Hang động
├── Hang hốc (Grotto)
├── Sông ngầm
├── Đường hầm Karst
└── Tuyến thám hiểm hang động

Nhưng ở đây có một sự phân chia quan trọng.

parentId có thể dùng cho phân loại nghiêm ngặt:

hang hốc là một dạng của đối tượng ngầm.

Còn KBFact dùng cho các mối quan hệ mềm:

similar_to
commonly_combined_with
may_trigger_same_avoidance
alternative_to

Ví dụ:

Địa đạo Củ Chi không phải là hang động, nhưng có thể không phù hợp với người mắc chứng sợ không gian hẹp.

Phân loại học (taxonomy) ở đây sẽ không giúp ích. Cần liên kết với khái niệm:

Không gian chật hẹp / Không gian kín

Sự thật (Fact):

Fact.type = "has_experience_attribute"

Địa đạo Củ Chi          role=subject
Không gian chật hẹp     role=attribute

Khi đó, việc loại trừ của người dùng được áp dụng theo thuộc tính trải nghiệm, chứ không chỉ theo danh mục.


Phải tách biệt loại đối tượng và tính chất trải nghiệm

Đây là điểm cốt lõi.

Ví dụ, "hang động" mô tả một đối tượng vật lý. Nhưng đối với người dùng, điều quan trọng hơn là:

  • tối;
  • chật chội;
  • ẩm ướt;
  • đòi hỏi thể lực;
  • tính phiêu lưu;
  • đi bộ đường dài;
  • nguy cơ bị bẩn;
  • không phù hợp khi mặc đầm dạ hội.

Do đó, cần các khái niệm kiểu experience_attribute:

indoor
outdoor
underground
water_based
physically_demanding
formal_friendly
muddy
crowded
quiet
romantic
family_friendly
weather_sensitive

Các mối liên kết:

Fact.type = "has_experience_attribute"
Fact.type = "requires_condition"
Fact.type = "conflicts_with_condition"

Ví dụ với đầm dạ hội:

Ngữ cảnh người dùng:
formal_clothing = true

Đối tượng:

Thác nước
has_experience_attribute = wet
has_experience_attribute = uneven_terrain
has_experience_attribute = outdoor

Hệ thống suy luận không phải vì "đầm dạ hội không tương thích với thác nước" được viết cứng thủ công, mà thông qua các đặc điểm (attributes).

Trong khi đó, nhà hàng, sòng bạc hay nhà hát lại có:

formal_friendly
indoor
evening_suitable


Nhu cầu của người dùng

Tôi sẽ thiết kế các nhu cầu dưới dạng các khái niệm (concepts):

Ăn uống
Ngủ nghỉ
Tắm biển
Mua quần áo
Giải trí cho trẻ em
Dành buổi tối
Ngắm thiên nhiên
Tránh di chuyển nhiều
Dành trọn ngày mưa
Trải nghiệm năng động
Thư giãn

Mối liên kết:

Fact.type = "satisfies_need"

place  role=provider
need   role=need

Ở phần tham gia (participation), bạn có thể dùng value để chỉ mức độ mạnh yếu:

value = "0.9"

Hoặc localImportance, mặc dù về mặt ngữ nghĩa thì không nên trộn lẫn tầm quan trọng của sự thật và mức độ thỏa mãn nhu cầu. Đối với các giá trị tính toán, tôi sẽ thêm trường data có cấu trúc vào sự thật hoặc một trường số riêng.

Ví dụ:

{
  "strength": 0.9,
  "capacity": "full",
  "notes": "Có thể dành trọn một ngày"
}

Hiện tại KBFact chưa có chung data, và đây là một trong số ít các phần mở rộng mà tôi thực sự cân nhắc.


Khu phức hợp và địa điểm thành phần

Bạn chắc chắn cần ngữ nghĩa của đối tượng phức hợp, nhưng không nhất thiết phải có mô hình Prisma riêng.

Khái niệm:

KBConcept.type = "place_complex"

Ví dụ:

VinWonders Nha Trang
Khu nghỉ dưỡng X
Vườn quốc gia Phong Nha
Cụm nghỉ dưỡng

Các thành phần là các địa điểm hoặc dịch vụ thông thường.

Mối liên kết:

Fact.type = "part_of_complex"

công viên nước role=component
VinWonders     role=complex

Hoặc bạn có thể tạo một sự thật phong phú hơn:

Fact.type = "complex_composition"

Những người tham gia:

VinWonders       role=complex
Công viên nước   role=component
Bãi biển         role=component
Khách sạn        role=component
Nhà hàng         role=component
Show diễn        role=component

Tôi vẫn thích tạo các sự thật riêng cho từng thành phần hơn, bởi vì:

  • các thành phần có giờ hoạt động khác nhau;
  • vé khác nhau;
  • thời hạn hiệu lực khác nhau;
  • nguồn gốc khác nhau;
  • mức độ tin cậy khác nhau.

Ví dụ:

Fact.type = "complex_component"
subject = Công viên nước
container = VinWonders
access_mode = included_ticket

Khi đó statement có thể do con người đọc được, còn các chi tiết nằm trong dữ liệu có cấu trúc.


"Có thể la cà suốt ba ngày" có nghĩa là gì

Đây không phải là một trường duration duy nhất.

Cần phân biệt:

  • thời gian tối thiểu để làm quen;
  • thời lượng điển hình;
  • thời lượng hữu ích tối đa;
  • có chỗ lưu trú hay không;
  • các hoạt động bên trong có đủ phong phú không;
  • có cần thiết phải đi ra ngoài đối tượng hay không.

Ví dụ, đối với VinWonders:

minimum_meaningful_duration = 1 day
recommended_duration = 2 days
maximum_stay_without_repetition = 3 days
overnight_available = true
self_contained = true

Điều này có thể được biểu diễn bằng vài sự thật:

recommended_duration
supports_overnight_stay
self_sufficiency
activity_capacity

self_sufficiency là một chỉ số đặc biệt quan trọng.

Ví dụ:

0.1 — một đài quan sát đơn lẻ
0.4 — điểm tham quan có quán cà phê và bãi đỗ xe
0.7 — khu nghỉ dưỡng trọn gói trong ngày
0.95 — tổ hợp nghỉ dưỡng kéo dài nhiều ngày

Nó có thể được tính toán từ:

  • ăn uống;
  • lưu trú;
  • số lượng hoạt động;
  • sự đa dạng của hoạt động;
  • chương trình buổi tối;
  • cơ sở hạ tầng sinh hoạt;
  • phương tiện di chuyển nội khu;
  • khả năng che chắn thời tiết.

Tôi sẽ không lưu trữ nó hoàn toàn bằng tay. Tốt hơn là lưu trữ các sự thật gốc, còn điểm số tổng hợp sẽ được tạo dưới dạng KBFactType.derived kèm theo derivedFrom.

Bạn đã có sẵn factTypederivedFrom cho việc này.


Các liên kết địa điểm (Sự kết hợp)

Mô hình của bạn cho phép làm cho chúng phong phú hơn rất nhiều so với placeAId/placeBId đơn thuần.

Liên kết cặp đơn giản

Fact.type = "works_well_together"

Địa điểm A  role=place
Địa điểm B  role=place

Nhưng tốt hơn là nên bổ sung ngữ cảnh ngay lập tức:

Địa điểm A        role=place
Địa điểm B        role=place
Một ngày          role=duration
Gia đình có trẻ em role=audience
Thời tiết khô ráo role=condition

Khi đó sự thật có nghĩa là:

Các địa điểm này kết hợp rất tốt trong một ngày cho gia đình có trẻ em vào ngày thời tiết khô ráo.

Tập hợp nhiều địa điểm

Mô hình n-ary đặc biệt hữu ích ở đây:

Fact.type = "recommended_visit_cluster"

Núi           role=anchor
Suối          role=optional_stop
Hang động     role=optional_stop
Nhà hàng      role=meal_stop
Khu nghỉ dưỡng role=overnight_base
Một rưỡi ngày role=recommended_duration

Đây đã là một phân đoạn hành trình hoàn chỉnh, nhưng chưa phải là hành trình của người dùng.


Tôi sẽ tách biệt cụm (cluster) và hành trình (route)

Đây là hai thực thể khác nhau.

Cụm (Cluster)

Sự liên kết khách quan hoặc theo góc nhìn biên tập:

Các đối tượng này về mặt địa lý và kịch bản tạo thành một tổ hợp tham quan thống nhất.

Loại:

KBConcept.type = "visit_cluster"

Ví dụ:

  • núi + suối + hang động + nhà hàng;
  • phố cổ + chợ + bờ sông;
  • đảo + bãi biển + công viên giải trí + khách sạn.

Hành trình (Route)

Thứ tự cụ thể:

Đầu tiên là núi, tiếp theo là suối, sau đó là nhà hàng, ngủ đêm tại khu nghỉ.

Loại:

KBConcept.type = "route"

Hành trình cần các phân đoạn được sắp xếp thứ tự.

KBFactParticipation hiện tại của bạn tự nó không lưu trữ thứ tự. Bạn có thể dùng value như 1, 2, 3, nhưng cách này không được sạch sẽ cho lắm.

Tôi sẽ bổ sung vào KBFactParticipation:

position Int?
data     Json?

Khi đó một sự thật kiểu route_composition có thể chứa:

Núi       role=stop position=1
Suối      role=stop position=2
Nhà hàng  role=stop position=3
Khu nghỉ  role=stop position=4

Và trong data của mỗi lượt tham gia:

{
  "arrivalTime": "09:00",
  "durationMinutes": 120,
  "optional": false
}

Đây là một trong những phần mở rộng có tính thực tiễn cao nhất cho schema của bạn.


Sở thích của người dùng

Có thể lưu trữ theo hai cách.

Dưới dạng sự thật của người dùng (User facts)

Người dùng cũng là một khái niệm hoặc được liên kết với khái niệm hồ sơ (profile).

Ví dụ:

Fact.type = "user_preference"

UserConcept       role=subject
Hang động         role=target
Không thích       role=preference

Nhưng tốt hơn là chuẩn hóa thành:

likes
dislikes
avoids
requires
prefers
neutral_to

Ví dụ:

Fact.type = "avoids"

Người dùng        role=subject
Không gian kín    role=target

Khi đó sẽ loại trừ:

  • hang động;
  • hang hốc;
  • đường hầm;
  • đền thờ ngầm;

nếu chúng liên kết với thuộc tính trải nghiệm (experience attribute) này.

Dưới dạng không gian tri thức của hành trình

Đối với một chuyến đi cụ thể, bạn có thể tạo KBKnowledgeSpace:

Chuyến đi Việt Nam, tháng 8 năm 2026

Các thông tin được chiếu (project) vào đây:

  • sở thích;
  • ràng buộc;
  • những người tham gia;
  • ngày tháng;
  • ngân sách;
  • các địa điểm đã chọn;
  • các sự thật hiện tại;
  • kết quả của agent.

Cách này tích hợp cực kỳ tự nhiên vào mô hình của bạn.


Không phải mọi sở thích đều mang tính toàn cục

Ví dụ:

  • người dùng nhìn chung thích thác nước;
  • nhưng hôm nay họ đang mặc trang phục dạ hội;
  • trong chuyến đi này họ đi cùng trẻ nhỏ;
  • ngày mai họ chỉ có ba tiếng;
  • trời đang mưa.

Do đó cần các ngữ cảnh khác nhau:

Hồ sơ người dùng toàn cục
Chuyến đi cụ thể
Ngày cụ thể
Phiên làm việc hiện tại

Chính KnowledgeSpace cho phép không biến điều kiện tạm thời "hôm nay tôi mặc đầm dạ hội" thành thuộc tính vĩnh viễn của người dùng.


Cách tính độ tương thích của địa điểm với truy vấn

Bạn có thể tách điểm số tổng (score) thành các thành phần:

intent_match
need_coverage
avoidance_conflict
context_fit
time_fit
geographic_fit
route_synergy
weather_fit
novelty
quality
confidence

Đại loại như:

score =
  0.22 * intent_match +
  0.18 * need_coverage +
  0.15 * geographic_fit +
  0.12 * time_fit +
  0.12 * route_synergy +
  0.08 * weather_fit +
  0.08 * quality +
  0.05 * novelty
  - hard_conflicts

Nhưng tôi sẽ không cố định một công thức duy nhất mãi mãi. Trọng số phụ thuộc vào kịch bản.

Đối với truy vấn "tìm nhà hàng gần đây":

  • vị trí địa lý — cao;
  • đang mở cửa — cao;
  • phù hợp ẩm thực — cao;
  • tính độc đáo — thứ yếu.

Đối với hành trình hai tuần:

  • sự đa dạng;
  • logic di chuyển (logistics);
  • sức chứa thời gian;
  • tính mùa vụ;
  • cân bằng hành trình.

Tốt hơn là biểu diễn chính hồ sơ đánh giá (scoring profile) dưới dạng một khái niệm:

restaurant_now_profile
family_day_trip_profile
multi_day_route_profile
formal_evening_profile

Và các trọng số dưới dạng sự thật hoặc data.


Ràng buộc cứng và ràng buộc mềm

Đây là điều bắt buộc phải phân tách.

Ràng buộc cứng (Hard constraints)

Đối tượng tuyệt đối không được xuất hiện trong kết quả:

  • người dùng loại trừ các địa điểm ngầm;
  • đối tượng đã đóng cửa;
  • không đủ thời gian;
  • trẻ em không đủ độ tuổi yêu cầu;
  • không thể di chuyển tới;
  • vượt quá ngân sách;
  • không tương thích với hạn chế thể lực.

Ràng buộc mềm (Soft constraints)

Đối tượng có thể xuất hiện ở vị trí thấp hơn:

  • xa hơn mong muốn;
  • đắt hơn một chút;
  • ít liên quan hơn;
  • tương tự với địa điểm đã chọn;
  • yêu cầu trang phục đặc biệt;
  • phụ thuộc vào thời tiết.

KBConstraint của bạn phù hợp cho các quy tắc khai báo (declarative rules), còn KBConflict dùng để ghi nhận các vi phạm.

Ví dụ:

constraint:
"Hành trình của người dùng tránh các trải nghiệm ngầm (underground experience)
không được chứa các địa điểm có thuộc tính underground."

Tuy nhiên, tôi sẽ không tạo KBConflict cho mọi ứng viên bị hệ thống gợi ý loại bỏ. Xung đột chỉ cần thiết cho những mâu thuẫn tri thức quan trọng hoặc một hành trình đã được hình thành sẵn, chứ không phải cho việc lọc thông thường.


Những gì cần thêm vào schema ở mức tối thiểu

Bạn đã có sẵn nền tảng. Tôi sẽ không thực hiện tái cấu trúc lớn.

1. Dữ liệu có cấu trúc cho sự thật (Fact data)

model KBFact {
  ...
  data Json?
}

Cần thiết cho:

  • các giá trị số;
  • đơn vị đo lường;
  • các khoảng giá trị (ranges);
  • tham số liên kết;
  • các đặc trưng thuật toán.

Ví dụ:

{
  "minMinutes": 180,
  "recommendedMinutes": 360,
  "maxMinutes": 720
}

2. Dữ liệu và thứ tự tham gia (Participation data and order)

model KBFactParticipation {
  ...
  position Int?
  data     Json?
}

Cần thiết cho:

  • hành trình (routes);
  • các cụm (clusters);
  • trình tự;
  • các tham số khác nhau của những người tham gia trong cùng một sự thật.

3. Chỉ mục (Index) theo trạng thái và thời gian

Cho các truy vấn hiện trạng thường xuyên:

@@index([status])
@@index([validFrom, validTo])
@@index([type, status])

Hiện tại trong schema được dẫn chứng, KBFact chỉ đánh chỉ mục trên type. Đối với nút "Cần biết gì lúc này", các truy vấn theo tính hiệu lực và trạng thái sẽ xuất hiện rất thường xuyên.

4. Chỉ mục không gian (Spatial index)

Hiện tại lat/lng đang được đánh chỉ mục bằng B-tree kết hợp thông thường. Đối với tính năng "có gì ở gần" thực tế, PostGIS sẽ tốt hơn:

geography(Point, 4326)
GIST index

Bạn có thể giữ lat/lng để tiện cho Prisma, đồng thời lưu trữ thêm điểm địa lý (geographic point) hoặc chuyển sang extension raw SQL.

Với kinh nghiệm của bạn, đây có lẽ là bước hiển nhiên: đối với các truy vấn bán kính (radius) và hành trình, chỉ mục tọa độ thông thường sẽ nhanh chóng trở thành điểm nghẽn.

5. Từ vựng loại ổn định (Stable type registry)

Các trường KBConcept.type, KBFact.type, KBFactParticipation.role là dạng chuỗi. Điều này mang lại sự linh hoạt, nhưng rất dễ dẫn đến tình trạng:

located_in
located-at
location
belongs_to_city

Do đó cần có một registry trong mã nguồn:

export const ConceptType = {
  Place: 'place',
  PlaceComplex: 'place_complex',
  City: 'geo_city',
  Need: 'need',
  Scenario: 'scenario',
} as const;

Tương tự cho các loại fact types và roles.


Những gì không nên đặt vào KBConcept.data

Về mặt kỹ thuật, bạn có thể nhét mọi thứ vào đó:

{
  "recommendedDuration": 180,
  "familyFriendly": true,
  "hasRestaurant": true
}

Nhưng khi đó đồ thị sẽ ngừng việc giải thích nguồn gốc dữ liệu một cách chính xác.

Tôi chỉ sử dụng data của concept cho:

  • metadata kỹ thuật;
  • tham số hiển thị;
  • các cấu trúc ít khi dùng đến;
  • bộ nhớ đệm (cache);
  • các kết quả đã khử chuẩn hóa (denormalized results).

Còn các khẳng định như:

  • phù hợp cho trẻ em;
  • có nhà hàng;
  • khuyên đi hai ngày;
  • đóng cửa đến tháng Chín;

nên được giữ dưới dạng các sự thật (facts).

Bởi vì chỉ có sự thật mới có:

  • nguồn gốc;
  • độ tin cậy;
  • thời gian;
  • trạng thái kiểm tra;
  • các xung đột;
  • provenance (truy nguyên nguồn gốc).

Cách agent sẽ tổng hợp trang thông tin địa điểm

Đối với một thẻ (card) duy nhất, truy vấn có thể thực hiện theo từng lớp.

Lớp cơ sở (Base layer)

Khái niệm:

  • tên;
  • mô tả;
  • hình ảnh;
  • tọa độ;
  • URL.

Phân loại (Taxonomy)

Các sự thật:

  • loại địa điểm;
  • vùng/tỉnh;
  • thành phố;
  • khu phức hợp;
  • hoạt động.

Tính hữu ích cho người dùng

  • các nhu cầu được đáp ứng;
  • đối tượng phù hợp;
  • các ràng buộc;
  • thời lượng khuyên dùng;
  • thời điểm đẹp nhất.

Các đối tượng liên quan

  • các bộ phận của khu phức hợp;
  • địa điểm ở gần;
  • địa điểm tương thích;
  • các lựa chọn thay thế;
  • hành trình;
  • các cụm (clusters).

Tính cập nhật (Relevance)

  • các sự thật tạm thời đang hiệu lực;
  • tin tức mới nhất;
  • thời tiết;
  • sự kiện;
  • các xung đột dữ liệu.

Cá nhân hóa

Thông qua không gian chuyến đi hiện tại:

  • cái gì phù hợp;
  • cái gì cần loại trừ;
  • đưa vào vị trí nào trong hành trình;
  • nên dành bao nhiêu thời gian;
  • nên thêm những địa điểm nào cùng lúc.

Và tất cả những điều này có thể hiển thị trên một trang duy nhất mà không bắt người dùng phải click vào danh mục để tìm kiếm.


Điểm mạnh nhất trong schema của bạn

Một cơ sở dữ liệu du lịch thông thường nói:

Đây là một hang động ở tỉnh nọ tỉnh kia.

Mô hình của bạn có tiềm năng nói rằng:

Đây là một quần thể thiên nhiên gần Đà Nẵng, bao gồm hang động, suối, nhà hàng và khu cắm trại. Đối với một cặp đôi năng động, nó phù hợp trong một rưỡi ngày, nhưng không khuyến khích cho người có nỗi sợ không gian kín. Vào tháng Tám, việc ghé thăm con suối phụ thuộc vào lượng mưa. Quần thể này hợp lý khi kết hợp với núi X, nhưng không nên kết hợp với đối tượng Y trong cùng một ngày.

Hơn nữa, mỗi khẳng định sẽ có:

  • nguồn gốc;
  • thời gian có hiệu lực;
  • độ tin cậy;
  • trạng thái;
  • các bên tham gia;
  • ngữ cảnh.

Đây không còn là CMS hay một danh mục thông thường nữa. Đây thực sự là một cơ sở dữ liệu suy luận (reasoning database).

Tổng kết của tôi

Bạn không cần phải thiết kế một cơ sở dữ liệu du lịch mới đè lên cái cũ.

Chỉ cần:

  1. Cố định hệ thống ngữ nghĩa du lịch cho KBConcept.type.
  2. Tạo từ vựng các loại sự thật và vai trò.
  3. Thêm data vào KBFact.
  4. Thêm positiondata vào KBFactParticipation.
  5. Đưa PostGIS vào để xử lý quan hệ không gian.
  6. Tách biệt rõ ràng:
    • loại đối tượng;
    • thuộc tính trải nghiệm;
    • nhu cầu;
    • ràng buộc;
    • khu phức hợp;
    • cụm (clusters);
    • hành trình (routes).
  7. Sử dụng các sự thật dạng derived cho các chỉ số tính toán như tính tự cung tự cấp, sức chứa hành trình và mật độ hữu ích.

Điều quan trọng nhất: đừng cố gắng ghi cứng trước tất cả các liên kết cặp giữa mọi địa điểm. Những thứ nên lưu trữ là các đặc trưng nền tảng và các liên kết biên tập có ý nghĩa, còn phần lớn độ tương thích nên được tính toán từ đồ thị theo từng người dùng cụ thể và từng hành trình cụ thể.

13.07.2026