{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-10-22T20:07:32Z","object_id":{"id":3114,"is_a":["text","documentation","reference","module"],"name":"doc_external_11_mod_wikiconcept","title":"mod_wikiconcept","uri":"https:\/\/zotonic.com\/id\/3114"},"seq":1}],"predicate":{"id":333,"is_a":["meta","predicate"],"name":"in_module","title":{"_type":"trans","tr":{"en":"In module"}},"uri":"https:\/\/zotonic.com\/id\/in_module"}},"subject":{"objects":[{"created":"2026-10-22T20:07:32Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-10-22T20:07:32Z","object_id":{"id":2563,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_frontend_developer","title":"Frontend developer","uri":"https:\/\/zotonic.com\/id\/2563"},"seq":2},{"created":"2026-10-22T20:07:32Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":3},{"created":"2026-10-22T20:07:32Z","object_id":{"id":2586,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_search_and_discovery","title":"Search and discovery","uri":"https:\/\/zotonic.com\/id\/2586"},"seq":4},{"created":"2026-10-22T20:07:32Z","object_id":{"id":2599,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_api_and_integration","title":"API and integration","uri":"https:\/\/zotonic.com\/id\/2599"},"seq":5},{"created":"2026-10-22T20:07:32Z","object_id":{"id":2625,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_model","title":"Model","uri":"https:\/\/zotonic.com\/id\/2625"},"seq":6},{"created":"2026-10-22T20:07:32Z","object_id":{"id":2678,"is_a":["categorization","keyword","keyword_technology"],"name":"zotonic_topic_semantic_web","title":"Semantic Web","uri":"https:\/\/zotonic.com\/id\/2678"},"seq":7}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":3115,"is_a":["text","documentation","reference","model"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3115"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_external_11_m_wikiconcept","page_url":{"en":"https:\/\/zotonic.com\/docs\/3115\/m_wikiconcept","x-default":"https:\/\/zotonic.com\/docs\/3115\/m_wikiconcept"},"preview_url":null,"resource":{"version":17,"pivot_location_lat":null,"title":"m_wikiconcept","is_authoritative":true,"body":"<p>Look up imported Wikidata\/OpenAlex concepts, search their names and connect\nthem to keyword resources. Concept rows are binary-keyed maps, with properties\nfrom <code>props_json<\/code> merged into the result. Imported text is not guaranteed to\nbe safe HTML; escape it when displayed.<\/p>\n<h2>Read paths<\/h2>\n<table class=\"table\"><thead><tr><th>Path under <code>m.wikiconcept<\/code><\/th><th>Result<\/th><\/tr><\/thead><tbody><tr><td><code>id[identifier]<\/code><\/td><td>One concept by Wikidata Q-code, OpenAlex C-code or stored HTTPS URL.<\/td><\/tr><tr><td><code>find[identifier]<\/code><\/td><td>Alias for <code>id<\/code>; this is not a free-text search.<\/td><\/tr><tr><td><code>find_keyword[identifier]<\/code><\/td><td>Best matching connected keyword resource ID.<\/td><\/tr><tr><td><code>connected[resource_id]<\/code><\/td><td>List of concepts connected to a resource.<\/td><\/tr><tr><td><code>list_all<\/code><\/td><td>Concepts ordered by local numeric ID, using request arguments <code>page<\/code> and <code>pagelen<\/code> (defaults 1 and 50).<\/td><\/tr><tr><td><code>total<\/code><\/td><td>Number of imported concepts.<\/td><\/tr><\/tbody><\/table>\n<p>For example:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% for concept in m.wikiconcept.connected[id] %}\n    {{ concept.display_name|escape }}\n{% endfor %}\n<\/code><\/pre>\n<p>Identifiers accept Q\/C prefixes in either case, or an exact stored Wikidata,\nOpenAlex or Wikipedia HTTPS URL. A local numeric concept ID is not accepted\nby <code>get_concept\/2<\/code>. Successful model reads return <code>{ok, {Value, Rest}}<\/code>;\nunknown identifiers return <code>{error, enoent}<\/code>. Unsupported model paths have\nno catch-all clause. Read paths do not check resource visibility or module\npermissions, including when returning connected keyword IDs.<\/p>\n<p><code>find_keyword\/2<\/code> first uses the concept&#39;s own keyword, then the connected\nancestor with the greatest level, then a connected descendant with the\nsmallest level. It returns <code>{ok, ResourceId}<\/code> or <code>{error, enoent}<\/code>.<\/p>\n<h2>Write paths<\/h2>\n<p>POST to these model paths with a binary-keyed payload map:<\/p>\n<table class=\"table\"><thead><tr><th>Path<\/th><th>Payload<\/th><th>Behavior<\/th><\/tr><\/thead><tbody><tr><td><code>insert\/keyword<\/code><\/td><td><code>wikidata<\/code><\/td><td>Return or create a keyword for the concept, returning <code>{ok, ResourceId}<\/code>.<\/td><\/tr><tr><td><code>connect<\/code><\/td><td><code>wikidata<\/code>, <code>id<\/code><\/td><td>Connect the concept to the keyword resource, replacing a previous connection.<\/td><\/tr><tr><td><code>disconnect<\/code><\/td><td><code>wikidata<\/code>, <code>id<\/code><\/td><td>Disconnect the concept from the keyword resource.<\/td><\/tr><\/tbody><\/table>\n<p>For example, the HTTP model endpoint for connecting is\n<code>\/api\/model\/wikiconcept\/post\/connect<\/code>, with a JSON body such as\n<code>{&quot;wikidata&quot;:&quot;Q420&quot;,&quot;id&quot;:1234}<\/code>. The transport&#39;s authentication and topic\nACLs apply independently of the model checks.<\/p>\n<p>Connect\/disconnect require <code>z_acl:rsc_editable\/2<\/code> and a resource in the keyword\ncategory; failures return <code>eacces<\/code> or <code>enoent<\/code>. Their direct Erlang helpers do\nnot repeat these ACL checks. Keyword insertion returns an existing connection\nor matching resource URI when available; otherwise it calls <code>m_rsc:insert\/2<\/code>,\ntrying the system content group and then the default group on <code>eacces<\/code>.\nReusing a matching resource URI requires an editable keyword resource.\n<code>insert_keyword\/3<\/code> accepts a category within the keyword subtree;\n<code>insert_keyword\/2<\/code> defaults to <code>keyword<\/code>. Resource insertion checks the selected\ncategory and content group permissions. An already connected keyword is returned unchanged.<\/p>\n<p>For direct Erlang calls with a selected keyword subcategory:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">m_wikiconcept:insert_keyword(&lt;&lt;&quot;Q420&quot;&gt;&gt;, CategoryId, Context).\n<\/code><\/pre>\n<p>The model POST path uses the default <code>keyword<\/code> category; category selection is\navailable through <code>insert_keyword\/3<\/code> and the admin concept dialog.<\/p>\n<p>New keywords are published and use the concept&#39;s translated <code>title<\/code> and <code>summary<\/code>.\nEach field retains nonempty translations matching the site&#39;s editable languages.\nIf none match, a nonempty English translation is copied to\n<code>z_language:default_language\/1<\/code>, independently of the current request language.\nFor titles, missing English text falls back to <code>display_name<\/code>, then the supplied\nconcept identifier. The resource&#39;s <code>language<\/code> property follows the resulting title\ntranslations. A summary without a matching or English translation is omitted.<\/p>\n<h2>Local search and remote import<\/h2>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">m_wikiconcept:find(&lt;&lt;&quot;biology&quot;&gt;&gt;, 1, 20, Context).\nm_wikiconcept:get_concept(&lt;&lt;&quot;Q420&quot;&gt;&gt;, Context).\n<\/code><\/pre>\n<p><code>find\/2<\/code> defaults to offset 1 and limit 20. <code>find\/4<\/code> uses a one-based offset,\nnormalizes text and matches words against local names, using PostgreSQL\ntrigram similarity for scoring. Empty search text lists concepts by display\nname. Results are <code>{ok, [ConceptMap]}<\/code> or an error. <code>count\/2<\/code> returns the exact\nnumber of matching concepts using the same criteria, without pagination.<\/p>\n<p><code>find_descendant\/2<\/code> defaults to offset 1 and limit 1000; <code>\/4<\/code> accepts explicit\npagination. It finds descendants one level below the first matching concept.\n<code>list_all_concepts\/3<\/code> instead accepts a one-based page number and page size.<\/p>\n<p><code>fetch_all\/1,2<\/code> fetch OpenAlex pages and return\n<code>{ok, {Concepts, done | {next, Cursor}}}<\/code> or an error. <code>load\/1<\/code> downloads and\nstores the complete catalogue. Remote <code>fetch\/2<\/code>, <code>openalex_search\/3<\/code> and\n<code>openalex_autocomplete\/3<\/code> return provider response data through <code>z_fetch<\/code>;\nthey are separate from the local model lookup paths.<\/p>\n<p>Installation and migration preserve existing rows and keyword links.\n<code>install_data\/1<\/code> queues an import only for an empty catalogue; use the admin\nReimport action to refresh populated tables. Imports update records by their\nWikidata URL and reconcile search names while retaining keyword connections.<\/p>","slug":"m_wikiconcept","doc_module_config":[],"is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"1083071a69d7bb753d9f0e742dc21f0f913d553d4f41fc1cfece83ef5828cb57","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"external_module_id":{"id":11,"is_a":[],"name":null,"title":null,"uri":"https:\/\/zotonic.com\/id\/11"},"git_url":"https:\/\/github.com\/driebit\/zotonic_mod_wikiconcept.git","category_id":{"id":322,"is_a":["meta","category"],"name":"model","title":"Models","uri":"https:\/\/test.zotonic.com\/id\/322"},"doc_source_path":".\/src\/models\/m_wikiconcept.erl","publication_start":"2026-10-22T20:07:32Z","github_url":"https:\/\/github.com\/driebit\/zotonic_mod_wikiconcept.git","pivot_location_lng":null,"doc_source_kind":"external","name":"doc_external_11_m_wikiconcept","is_unfindable":false,"is_published":true,"pivot_geocode":null,"external_module_title":"Wikidata Concepts","created":"2026-10-22T20:07:32Z","uri":null,"doc_status":"current","is_dependent":false,"is_external_module":true,"publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"5f9530a8bf56b6bc3df48ab462c1c2d55af14472","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-22T20:07:33Z","title_slug":"m_wikiconcept"},"uri":"https:\/\/zotonic.com\/id\/3115","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3115"}},"status":"ok"}