diff --git a/DATABASE.md b/DATABASE.md
index cbefe983b..5d991586c 100644
--- a/DATABASE.md
+++ b/DATABASE.md
@@ -33,7 +33,6 @@ Further tables are:
- `special_morphisms`
- `related_category_properties`
- `category_comments`
-- `category_property_comments`
For functors there are similar tables, such as:
diff --git a/databases/catdat/data/categories/FreeAb.yaml b/databases/catdat/data/categories/FreeAb.yaml
index fdb0bd425..984e6e75f 100644
--- a/databases/catdat/data/categories/FreeAb.yaml
+++ b/databases/catdat/data/categories/FreeAb.yaml
@@ -57,9 +57,9 @@ unsatisfied_properties:
- property: ℵ₁-filtered colimits
reason: It is shown in MO/511426 that the $\aleph_1$-filtered diagram of countable subgroups of $\IZ^{\IN}$ does not have a colimit in $\FreeAb$.
-category_property_comments:
+undecidable_properties:
- property: accessible
- comment: The question if this category is accessible is undecidable in ZFC. See MSE/720885.
+ reason: The question if this category is accessible is undecidable in ZFC. See MSE/720885.
special_objects:
initial object:
diff --git a/databases/catdat/data/categories/M-Set.yaml b/databases/catdat/data/categories/M-Set.yaml
index 5a9185843..0ad479010 100644
--- a/databases/catdat/data/categories/M-Set.yaml
+++ b/databases/catdat/data/categories/M-Set.yaml
@@ -31,9 +31,9 @@ unsatisfied_properties:
- property: trivial
reason: This is trivial.
-category_property_comments:
+undecidable_properties:
- property: semi-strongly connected
- comment: If this category is semi-strongly connected depends on the choice of $M$. For $M = 1$ it is, for $M = \IZ$ it is not. In general, if $G$ is a group, then $G{-}\Set$ is semi-strongly connected if and only if for all subgroups $H,K \subseteq G$, $H$ is subconjugated to $K$ or $K$ is subconjugated to $H$. If $G$ is abelian, this means that the poset of subgroups is linear, in which case $G$ is either isomorphic to $\IZ/p^n$ or to $\IZ/p^{\infty}$ for a prime $p$. See also MSE/5129804.
+ reason: If this category is semi-strongly connected depends on the choice of $M$. For $M = 1$ it is, for $M = \IZ$ it is not. In general, if $G$ is a group, then $G{-}\Set$ is semi-strongly connected if and only if for all subgroups $H,K \subseteq G$, $H$ is subconjugated to $K$ or $K$ is subconjugated to $H$. If $G$ is abelian, this means that the poset of subgroups is linear, in which case $G$ is either isomorphic to $\IZ/p^n$ or to $\IZ/p^{\infty}$ for a prime $p$. See also MSE/5129804.
special_objects:
initial object:
diff --git a/databases/catdat/schema/004_category-properties.sql b/databases/catdat/schema/004_category-properties.sql
index 7714816da..f5eca8641 100644
--- a/databases/catdat/schema/004_category-properties.sql
+++ b/databases/catdat/schema/004_category-properties.sql
@@ -23,8 +23,9 @@ CREATE TABLE category_property_assignments (
id INTEGER PRIMARY KEY,
category_id TEXT NOT NULL,
property_id TEXT NOT NULL,
- is_satisfied INTEGER NOT NULL
- CHECK (is_satisfied in (TRUE, FALSE)),
+ is_satisfied INTEGER
+ -- we use NULL for undecidable properties
+ CHECK (is_satisfied in (TRUE, FALSE, NULL)),
reason TEXT NOT NULL CHECK (length(reason) > 0),
is_deduced INTEGER NOT NULL DEFAULT FALSE
CHECK (is_deduced in (TRUE, FALSE)),
diff --git a/databases/catdat/schema/010_functor-properties.sql b/databases/catdat/schema/010_functor-properties.sql
index 256030aac..25aba0200 100644
--- a/databases/catdat/schema/010_functor-properties.sql
+++ b/databases/catdat/schema/010_functor-properties.sql
@@ -15,8 +15,9 @@ CREATE TABLE functor_property_assignments (
id INTEGER PRIMARY KEY,
functor_id TEXT NOT NULL,
property_id TEXT NOT NULL,
- is_satisfied INTEGER NOT NULL
- CHECK (is_satisfied IN (TRUE, FALSE)),
+ is_satisfied INTEGER
+ -- we use NULL for undecidable properties
+ CHECK (is_satisfied in (TRUE, FALSE, NULL)),
reason TEXT NOT NULL CHECK (length(reason) > 0),
is_deduced INTEGER NOT NULL DEFAULT FALSE
CHECK (is_deduced in (TRUE, FALSE)),
diff --git a/databases/catdat/schema/013_property_comments.sql b/databases/catdat/schema/013_property_comments.sql
deleted file mode 100644
index 2bd07ac49..000000000
--- a/databases/catdat/schema/013_property_comments.sql
+++ /dev/null
@@ -1,8 +0,0 @@
-CREATE TABLE category_property_comments (
- category_id TEXT NOT NULL,
- property_id TEXT NOT NULL,
- comment TEXT NOT NULL,
- PRIMARY KEY (category_id, property_id),
- FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE,
- FOREIGN KEY (property_id) REFERENCES category_properties (id) ON DELETE CASCADE
-);
\ No newline at end of file
diff --git a/databases/catdat/scripts/deduce-entity-properties.ts b/databases/catdat/scripts/deduce-entity-properties.ts
index 9e8d6c074..55d6e997e 100644
--- a/databases/catdat/scripts/deduce-entity-properties.ts
+++ b/databases/catdat/scripts/deduce-entity-properties.ts
@@ -299,15 +299,17 @@ function deduce_unsatisfied_properties(
/**
* Assign dual properties to dual entities:
* If C has property P, then C^op has property P^op (if defined).
- * Warning: This mutates the sets of satisfied and unsatisfied properties.
+ * Warning: This mutates the sets of assigned properties.
*/
function deduce_dual_properties(
db: Database,
entity: EntityMeta & { dual: string },
satisfied: Set,
unsatisfied: Set,
+ undecidable: Set,
dual_satisfied: Set,
dual_unsatisfied: Set,
+ dual_undecidable: Set,
properties_dict: Record,
type: 'category',
) {
@@ -329,14 +331,24 @@ function deduce_dual_properties(
unsatisfied.add(p_dual)
}
+ const new_undecidable = new Set()
+
+ for (const p of dual_undecidable) {
+ const p_dual = properties_dict[p].dual
+ if (!p_dual || undecidable.has(p_dual)) continue
+ new_undecidable.add(p_dual)
+ undecidable.add(p_dual)
+ }
+
const property_insert = db.prepare(`
INSERT INTO ${type}_property_assignments
(${type}_id, property_id, is_satisfied, reason, is_deduced)
VALUES (?, ?, ?, ?, TRUE)
`)
- const reason_satisfied = 'Its dual ${type} satisfies the dual property.'
- const reason_unsatisfied = 'Its dual ${type} does not satisfy the dual property.'
+ const reason_satisfied = `Its dual ${type} satisfies the dual property.`
+ const reason_unsatisfied = `Its dual ${type} does not satisfy the dual property.`
+ const reason_undecidable = `The dual property is undecidable for its dual ${type}.`
for (const p of new_satisfied) {
property_insert.run(entity.id, p, 1, reason_satisfied)
@@ -353,6 +365,14 @@ function deduce_dual_properties(
console.info(
`Deduced ${new_unsatisfied.size} unsatisfied properties by duality for ${entity.id}`,
)
+
+ for (const q of new_undecidable) {
+ property_insert.run(entity.id, q, null, reason_undecidable)
+ }
+
+ console.info(
+ `Deduced ${new_undecidable.size} undecidable properties by duality for ${entity.id}`,
+ )
}
/**
@@ -375,19 +395,19 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') {
const implications = get_normalized_implications(db, type)
const entities = get_entities(db, type)
const properties_dict = get_properties_dict(db, type)
- const all_decided_properties = get_property_assignments(db, entities, type)
+ const get_assigned_properties = get_property_assignments(db, entities, type)
const deduction = db.transaction(() => {
delete_deduced_properties(db, type)
for (const entity of entities) {
- const decided = all_decided_properties[entity.id]
+ const assigned = get_assigned_properties[entity.id]
deduce_satisfied_properties(
db,
entity,
implications,
- decided.satisfied,
+ assigned.satisfied,
properties_dict,
type,
)
@@ -396,8 +416,8 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') {
db,
entity,
implications,
- decided.satisfied,
- decided.unsatisfied,
+ assigned.satisfied,
+ assigned.unsatisfied,
properties_dict,
type,
)
@@ -413,16 +433,18 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') {
for (const entity of entities) {
if (!is_dual_entity(entity)) continue
- const decided = all_decided_properties[entity.id]
- const dual_decided = all_decided_properties[entity.dual]
+ const assigned = get_assigned_properties[entity.id]
+ const dual_assigned = get_assigned_properties[entity.dual]
deduce_dual_properties(
db,
entity,
- decided.satisfied,
- decided.unsatisfied,
- dual_decided.satisfied,
- dual_decided.unsatisfied,
+ assigned.satisfied,
+ assigned.unsatisfied,
+ assigned.undecidable,
+ dual_assigned.satisfied,
+ dual_assigned.unsatisfied,
+ dual_assigned.undecidable,
properties_dict,
type,
)
@@ -431,7 +453,7 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') {
db,
entity,
implications,
- decided.satisfied,
+ assigned.satisfied,
properties_dict,
type,
)
@@ -440,8 +462,8 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') {
db,
entity,
implications,
- decided.satisfied,
- decided.unsatisfied,
+ assigned.satisfied,
+ assigned.unsatisfied,
properties_dict,
type,
)
diff --git a/databases/catdat/scripts/seed.ts b/databases/catdat/scripts/seed.ts
index b2afb9d50..e849af3d4 100644
--- a/databases/catdat/scripts/seed.ts
+++ b/databases/catdat/scripts/seed.ts
@@ -73,7 +73,6 @@ function clear_all_data() {
db.prepare(`DELETE FROM special_objects`).run()
db.prepare(`DELETE FROM special_object_types`).run()
- db.prepare(`DELETE FROM category_property_comments`).run()
db.prepare(`DELETE FROM category_property_assignments`).run()
db.prepare(`DELETE FROM category_comments`).run()
@@ -187,11 +186,6 @@ function seed_categories() {
) VALUES (?, ?, ?, ?, ?)`,
)
- const property_comment_insert = db.prepare(`
- INSERT INTO category_property_comments (
- category_id, property_id, comment
- ) VALUES (?, ?, ?)`)
-
function insert_category(category: CategoryYaml) {
category_insert.run(
category.id,
@@ -251,11 +245,13 @@ function seed_categories() {
)
}
- for (const comment_obj of category.category_property_comments ?? []) {
- property_comment_insert.run(
+ for (const entry of category.undecidable_properties ?? []) {
+ property_assignment_insert.run(
category.id,
- comment_obj.property,
- comment_obj.comment,
+ entry.property,
+ null,
+ entry.reason,
+ entry.check_redundancy === false ? 0 : 1,
)
}
}
diff --git a/databases/catdat/scripts/seed.types.ts b/databases/catdat/scripts/seed.types.ts
index 82a72bbde..74ab71acd 100644
--- a/databases/catdat/scripts/seed.types.ts
+++ b/databases/catdat/scripts/seed.types.ts
@@ -28,13 +28,10 @@ export type CategoryYaml = {
related_categories: string[]
satisfied_properties: PropertyEntry[]
unsatisfied_properties: PropertyEntry[]
+ undecidable_properties?: PropertyEntry[]
special_objects: Record
special_morphisms: Record
comments?: string[]
- category_property_comments?: {
- property: string
- comment: string
- }[]
}
type PropertyEntry = {
diff --git a/databases/catdat/scripts/test.ts b/databases/catdat/scripts/test.ts
index 8e8434794..3b088d4ef 100644
--- a/databases/catdat/scripts/test.ts
+++ b/databases/catdat/scripts/test.ts
@@ -150,6 +150,7 @@ function test_decided_entities(entities: string[], type: 'category' | 'functor')
* Tests if selected categories or functors behave as expected:
* All of their properties in the database have to match those in the
* respective JSON files in the subfolder "expected-data".
+ * We exclude undecidable properties here.
*/
function test_properties_of_selected_entities(
expected: Record>,
@@ -157,7 +158,7 @@ function test_properties_of_selected_entities(
) {
const property_query = db.prepare(
`SELECT property_id, is_satisfied FROM ${type}_property_assignments
- WHERE ${type}_id = ?`,
+ WHERE ${type}_id = ? AND is_satisfied IS NOT NULL`,
)
for (const entity_id in expected) {
diff --git a/databases/catdat/scripts/utils/deduction.ts b/databases/catdat/scripts/utils/deduction.ts
index 01d670a71..80462d6d2 100644
--- a/databases/catdat/scripts/utils/deduction.ts
+++ b/databases/catdat/scripts/utils/deduction.ts
@@ -74,7 +74,8 @@ export function get_properties_dict(db: Database, type: 'category' | 'functor')
/**
* Returns a dictionary with all assigned properties of a list of entities
- * (categories or functors), grouped by id and value (satisfied / unsatisfied).
+ * (categories or functors), grouped by id and
+ * value (satisfied / unsatisfied / undecidable).
*/
export function get_property_assignments(
db: Database,
@@ -86,18 +87,32 @@ export function get_property_assignments(
`SELECT property_id, ${type}_id as entity_id, is_satisfied
FROM ${type}_property_assignments`,
)
- .all() as { property_id: string; entity_id: string; is_satisfied: 0 | 1 }[]
+ .all() as { property_id: string; entity_id: string; is_satisfied: 0 | 1 | null }[]
- const grouped: Record; unsatisfied: Set }> =
- {}
+ const grouped: Record<
+ string,
+ { satisfied: Set; unsatisfied: Set; undecidable: Set }
+ > = {}
for (const entity of entities) {
- grouped[entity.id] = { satisfied: new Set(), unsatisfied: new Set() }
+ grouped[entity.id] = {
+ satisfied: new Set(),
+ unsatisfied: new Set(),
+ undecidable: new Set(),
+ }
}
for (const row of rows) {
const { property_id, entity_id, is_satisfied } = row
- grouped[entity_id][is_satisfied ? 'satisfied' : 'unsatisfied'].add(property_id)
+ let group_key: 'satisfied' | 'unsatisfied' | 'undecidable'
+ if (is_satisfied === 1) {
+ group_key = 'satisfied'
+ } else if (is_satisfied === 0) {
+ group_key = 'unsatisfied'
+ } else {
+ group_key = 'undecidable'
+ }
+ grouped[entity_id][group_key].add(property_id)
}
return grouped
@@ -106,6 +121,7 @@ export function get_property_assignments(
/**
* Returns a dictionary with all assigned properties of entities,
* grouped by entity, value (satisfied / unsatisfied), and deduced status.
+ * We exclude undecidable properties here.
*/
export function get_property_assignments_by_deduction(
db: Database,
@@ -115,7 +131,7 @@ export function get_property_assignments_by_deduction(
const rows = db
.prepare(
`SELECT property_id, ${type}_id as entity_id, is_satisfied, is_deduced
- FROM ${type}_property_assignments`,
+ FROM ${type}_property_assignments WHERE is_satisfied IS NOT NULL`,
)
.all() as {
property_id: string
diff --git a/src/components/PropertyList.svelte b/src/components/PropertyList.svelte
index 00a5d9e53..65da0434c 100644
--- a/src/components/PropertyList.svelte
+++ b/src/components/PropertyList.svelte
@@ -9,17 +9,16 @@
reason?: string | null
}[]
type: 'category' | 'functor'
- reason_heading?: string
}
- let { properties, type, reason_heading }: Props = $props()
+ let { properties, type }: Props = $props()
{#if properties.length}
{#each properties as { id, relation, reason }}
-
-
+
{relation}
{id}
diff --git a/src/components/TextWithReason.svelte b/src/components/TextWithReason.svelte
index 7ca3f4d06..7a8689406 100644
--- a/src/components/TextWithReason.svelte
+++ b/src/components/TextWithReason.svelte
@@ -7,10 +7,9 @@
type Props = {
children: Snippet
reason?: string | null
- heading?: string
}
- let { children, reason, heading = 'Reason' }: Props = $props()
+ let { children, reason }: Props = $props()
const id = $props.id()
@@ -18,7 +17,7 @@
e.stopPropagation()
show_popup({
id,
- heading,
+ heading: 'Reason',
text: reason!,
})
}
diff --git a/src/lib/server/utils.ts b/src/lib/server/utils.ts
index 94318c578..20d4ec3fd 100644
--- a/src/lib/server/utils.ts
+++ b/src/lib/server/utils.ts
@@ -1,7 +1,11 @@
import type {
+ CategoryProperty,
+ CategoryPropertyDB,
FunctorImplicationDB,
FunctorImplicationDisplay,
FunctorProperty,
+ FunctorPropertyAssignment,
+ FunctorPropertyAssignmentDB,
FunctorPropertyDB,
ImplicationDB,
ImplicationDisplay,
@@ -45,6 +49,17 @@ export function display_property(property: PropertyDB): PropertyDisplay {
}
}
+export function display_category_property_assignment(
+ property: CategoryPropertyDB,
+): CategoryProperty {
+ return {
+ id: property.id,
+ reason: property.reason,
+ is_deduced: Boolean(property.is_deduced),
+ relation: property.relation,
+ }
+}
+
export function display_functor_property(property: FunctorPropertyDB): FunctorProperty {
return {
id: property.id,
@@ -56,6 +71,17 @@ export function display_functor_property(property: FunctorPropertyDB): FunctorPr
}
}
+export function display_functor_property_assignment(
+ property: FunctorPropertyAssignmentDB,
+): FunctorPropertyAssignment {
+ return {
+ id: property.id,
+ reason: property.reason,
+ is_deduced: Boolean(property.is_deduced),
+ relation: property.relation,
+ }
+}
+
export function display_functor_implication(
implication: FunctorImplicationDB,
): FunctorImplicationDisplay {
diff --git a/src/routes/category-property/[id]/+page.server.ts b/src/routes/category-property/[id]/+page.server.ts
index 4d06e2246..86942ba12 100644
--- a/src/routes/category-property/[id]/+page.server.ts
+++ b/src/routes/category-property/[id]/+page.server.ts
@@ -15,7 +15,7 @@ export const load = async (event) => {
PropertyDB,
{ id: string },
ImplicationDB,
- CategoryShort & { is_satisfied: 0 | 1 },
+ CategoryShort & { is_satisfied: 0 | 1 | null },
CategoryShort,
]
>([
@@ -95,8 +95,9 @@ export const load = async (event) => {
const related_properties = related.map(({ id }) => id)
- const examples = known_categories.filter((c) => c.is_satisfied)
- const counterexamples = known_categories.filter((c) => !c.is_satisfied)
+ const examples = known_categories.filter((c) => c.is_satisfied === 1)
+ const counterexamples = known_categories.filter((c) => c.is_satisfied === 0)
+ const undecidable_categories = known_categories.filter((c) => c.is_satisfied === null)
const relevant_implications = relevant_implications_db.map(display_implication)
@@ -112,6 +113,7 @@ export const load = async (event) => {
examples,
counterexamples,
unknown_categories,
+ undecidable_categories,
relevant_implications,
})
}
diff --git a/src/routes/category-property/[id]/+page.svelte b/src/routes/category-property/[id]/+page.svelte
index 1362b55c6..8192fb62a 100644
--- a/src/routes/category-property/[id]/+page.svelte
+++ b/src/routes/category-property/[id]/+page.svelte
@@ -107,4 +107,17 @@
+{#if data.undecidable_categories.length}
+
Undecidable categories
+
+
+ {pluralize(data.undecidable_categories.length, {
+ one: 'There is {count} category for which it cannot be decided if this property is satisfied or not.',
+ other: 'There are {count} categories for which it cannot be decided if this property is satisfied or not.',
+ })}
+
+
+
+{/if}
+
diff --git a/src/routes/category/[id]/+page.server.ts b/src/routes/category/[id]/+page.server.ts
index bfe6207e8..e5a7c2e3f 100644
--- a/src/routes/category/[id]/+page.server.ts
+++ b/src/routes/category/[id]/+page.server.ts
@@ -4,7 +4,6 @@ import { batch } from '$lib/server/db.catdat'
import sql from 'sql-template-tag'
import type {
CategoryDisplay,
- CategoryProperty,
CategoryPropertyDB,
CategoryShort,
CommentObject,
@@ -14,6 +13,7 @@ import type {
SpecialObject,
TagObject,
} from '$lib/commons/types'
+import { display_category_property_assignment } from '$lib/server/utils'
export const load = async (event) => {
const id = event.params.id
@@ -23,7 +23,7 @@ export const load = async (event) => {
CategoryDisplay,
RelatedCategory,
TagObject,
- CategoryPropertyDB & { is_satisfied: 0 | 1 },
+ CategoryPropertyDB & { is_satisfied: 0 | 1 | null },
PropertyShort,
SpecialObject,
SpecialMorphism,
@@ -75,8 +75,8 @@ export const load = async (event) => {
cp.reason,
cp.is_deduced,
CASE
- WHEN cp.is_satisfied = TRUE THEN p.relation
- ELSE r.negation
+ WHEN cp.is_satisfied = FALSE THEN r.negation
+ ELSE p.relation
END AS relation
FROM category_property_assignments cp
INNER JOIN category_properties p ON p.id = cp.property_id
@@ -86,13 +86,8 @@ export const load = async (event) => {
`,
// unknown properties
sql`
- SELECT
- p.id,
- p.relation,
- c.comment as reason
+ SELECT p.id, p.relation
FROM category_properties p
- LEFT JOIN category_property_comments c
- ON c.category_id = ${id} AND c.property_id = p.id
WHERE NOT EXISTS (
SELECT 1 FROM category_property_assignments
WHERE category_id = ${id} AND property_id = p.id
@@ -159,23 +154,17 @@ export const load = async (event) => {
const category = categories[0]
const tags = tag_objects.map(({ tag }) => tag)
- const satisfied_properties: CategoryProperty[] = properties_db
- .filter((obj) => obj.is_satisfied)
- .map((p) => ({
- id: p.id,
- reason: p.reason,
- is_deduced: Boolean(p.is_deduced),
- relation: p.relation,
- }))
+ const satisfied_properties = properties_db
+ .filter((obj) => obj.is_satisfied === 1)
+ .map(display_category_property_assignment)
+
+ const unsatisfied_properties = properties_db
+ .filter((obj) => obj.is_satisfied === 0)
+ .map(display_category_property_assignment)
- const unsatisfied_properties: CategoryProperty[] = properties_db
- .filter((obj) => !obj.is_satisfied)
- .map((p) => ({
- id: p.id,
- reason: p.reason,
- is_deduced: Boolean(p.is_deduced),
- relation: p.relation,
- }))
+ const undecidable_properties = properties_db
+ .filter((obj) => obj.is_satisfied === null)
+ .map(display_category_property_assignment)
return render_nested_formulas({
category,
@@ -184,6 +173,7 @@ export const load = async (event) => {
satisfied_properties,
unsatisfied_properties,
unknown_properties,
+ undecidable_properties,
special_objects,
special_morphisms,
undistinguishable_categories,
diff --git a/src/routes/category/[id]/+page.svelte b/src/routes/category/[id]/+page.svelte
index 84f6e4298..958e0b401 100644
--- a/src/routes/category/[id]/+page.svelte
+++ b/src/routes/category/[id]/+page.svelte
@@ -157,13 +157,26 @@
{/if}
-
+
+{#if data.undecidable_properties.length > 0}
+
+ Undecidable properties
+
+ {#if data.undecidable_properties.length > 0}
+
+ {pluralize(data.undecidable_properties.length, {
+ one: 'There is {count} property for which it cannot be decided if it is satisfied or not.',
+ other: 'There are {count} properties for which it cannot be decided if they are satisfied or not.',
+ })}
+
+ {/if}
+
+
+
+{/if}
+
Special objects
diff --git a/src/routes/download/+page.svelte b/src/routes/download/+page.svelte
index 238107ff9..36c9b47ef 100644
--- a/src/routes/download/+page.svelte
+++ b/src/routes/download/+page.svelte
@@ -122,6 +122,12 @@ GROUP BY p.id
ORDER BY undecided_categories DESC LIMIT 10;
+-- Properties which cannot be decided for a given category
+SELECT category_id, property_id, reason
+FROM category_property_assignments
+WHERE is_satisfied IS NULL;
+
+