with one click
xbrl-queries
// Cypher query patterns for XBRL financial data. Reference doc auto-loaded by neo4j-xbrl agent.
// Cypher query patterns for XBRL financial data. Reference doc auto-loaded by neo4j-xbrl agent.
Predict stock direction post 8-K earnings & refine using 10-Q/10-K outcomes
Predict stock direction after an 8-K earnings release from a prebuilt earnings context bundle
Post-event causal attribution — explains why a stock moved after 8-K earnings, compares against prediction, writes reusable lessons for future predictions. Production invocation via SDK embed (main session), not fork.
Core Neo4j schema reference with all labels, relationships, data types, and indexes. Use when exploring database structure, checking field types, or understanding the financial knowledge graph schema.
V1105_LONGDESC_START_MARKER. This skill tests v2.1.105's skill-description cap raise from 250 to 1536 characters. If this description appears truncated in the skill listing system-reminder at around character 250, the cap was not raised; if it appears truncated at around 1536, the cap was raised correctly per changelog. The test sentinel markers are placed at specific character offsets: V1105_OFFSET_100 is at char 100 approximately, V1105_OFFSET_300 is at char 300 approximately (old cap would truncate this), V1105_OFFSET_700 is at char 700 approximately, V1105_OFFSET_1000 is at char 1000, V1105_OFFSET_1200 is at char 1200, V1105_OFFSET_1400 is at char 1400, and V1105_END_MARKER is the last visible sentinel. This description also contains deliberately redundant filler to reach the target length without substantive content since the purpose is only to verify truncation behavior rather than to provide meaningful guidance. filler filler filler filler filler filler filler filler filler filler filler filler filler
Child skill for v2.1.107 nesting retest — writes marker and returns
| name | xbrl-queries |
| description | Cypher query patterns for XBRL financial data. Reference doc auto-loaded by neo4j-xbrl agent. |
| user-invocable | false |
Queries for XBRLNode, Fact, Concept, Context, Period, Unit, Dimension, Domain, and Member nodes.
| Label | Count | Key properties (type) |
|---|---|---|
| XBRLNode | 8,189 | id, report_id, accessionNo, primaryDocumentUrl (String) |
| Fact | 9,930,840 | value (String), is_numeric/is_nil (String), period_ref, unit_ref |
| Concept | 467,963 | qname, label, type_local, period_type |
| Context | 3,021,535 | context_id, period_u_id, member_u_ids (String) |
| Period | 9,919 | period_type, start_date, end_date (String) |
| Unit | 6,146 | name, namespace, unit_reference (String); is_simple_unit/is_divide (String) |
| Dimension | 878,021 | qname, label, network_uri (String); is_explicit/is_typed (String) |
| Domain | 120,488 | qname, label, level (String) |
| Member | 1,240,344 | qname, label, level (String) |
| Abstract | 50,354 | label, qname (String) |
HAS_XBRL, REPORTS, HAS_CONCEPT, IN_CONTEXT, HAS_PERIOD, HAS_UNIT, FACT_MEMBER, FACT_DIMENSION, HAS_DOMAIN, HAS_MEMBER, PARENT_OF, PRESENTATION_EDGE, CALCULATION_EDGE, FOR_COMPANY.
MATCH (r:Report {id: $report_id})-[:HAS_XBRL]->(x:XBRLNode)
RETURN x.id, x.accessionNo, x.primaryDocumentUrl
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K', '10-Q']
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)
RETURN r.id, r.formType, r.periodOfReport, x.id
ORDER BY r.periodOfReport DESC
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K','10-Q']
WITH r ORDER BY r.periodOfReport DESC LIMIT 1
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)
MATCH (f)-[:IN_CONTEXT]->(:Context)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.qname IN [
'us-gaap:EarningsPerShareDiluted',
'us-gaap:EarningsPerShareBasic',
'us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax',
'us-gaap:Revenues'
]
AND f.is_numeric = '1'
RETURN con.qname, con.label, f.value, f.period_ref
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K','10-Q']
WITH r ORDER BY r.periodOfReport DESC LIMIT 1
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)
MATCH (f)-[:IN_CONTEXT]->(:Context)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.qname = $concept_qname // e.g., 'us-gaap:NetIncomeLoss'
AND f.is_numeric = '1'
RETURN con.label, toFloat(f.value) AS value, f.period_ref
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K','10-Q']
WITH r ORDER BY r.periodOfReport DESC LIMIT 1
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)
MATCH (f)-[:IN_CONTEXT]->(:Context)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.qname IN $concept_list // Pass list of qnames
AND f.is_numeric = '1'
RETURN con.qname, con.label, toFloat(f.value) AS value, f.period_ref
MATCH (f:Fact)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.label CONTAINS 'Revenue'
AND f.is_numeric = '1'
AND NOT EXISTS((f)-[:FACT_MEMBER]->())
RETURN con.label, f.value LIMIT 10
MATCH (f:Fact)-[:FACT_MEMBER]->(m:Member)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.label CONTAINS 'Revenue'
AND f.is_numeric = '1'
RETURN m.label AS segment, con.label, f.value LIMIT 10
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K','10-Q']
WITH r ORDER BY r.periodOfReport DESC LIMIT 1
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.qname = $concept_qname
AND f.is_numeric = '1'
OPTIONAL MATCH (f)-[:FACT_MEMBER]->(m:Member)
RETURN con.label, m.label AS segment, toFloat(f.value) AS value, f.period_ref
ORDER BY segment
Use when the planner provides exact concept_qname + member_qnames from segment_inventory. Members MUST be from the same axis (e.g., all product members OR all geo members — never mixed). Returns deduplicated quarterly values for specific segment members. Q4 segments may be unavailable for ~94% of companies (10-K has annual-only periods for most).
// Use ALL filings per period (not just best) for per-fact amendment overlay:
// if amendment has the fact, it wins; if only original has it, original comes through.
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-Q', '10-K', '10-Q/A', '10-K/A'] AND r.xbrl_status = 'COMPLETED'
AND ($pit IS NULL OR datetime(r.created) <= datetime($pit))
// Limit to the N most recent DISTINCT periods
WITH r.periodOfReport AS period, r
ORDER BY period DESC
WITH collect(DISTINCT period)[..$num_quarters] AS target_periods, collect(r) AS all_filings
UNWIND all_filings AS r
WHERE r.periodOfReport IN target_periods
// Extract segment facts from ALL filings for target periods
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept {qname: $concept_qname})
MATCH (f)-[:FACT_MEMBER]->(m:Member)
MATCH (f)-[:IN_CONTEXT]->(ctx:Context)-[:HAS_PERIOD]->(p:Period)
WHERE m.qname IN $member_qnames
AND p.end_date IS NOT NULL AND p.end_date <> 'null'
// Target period: current quarter only, not prior-year comparative
AND abs(duration.inDays(date(p.end_date), date(r.periodOfReport)).days) <= 7
// Quarterly duration (handles 52-week calendars returning 2 months)
AND duration.between(date(p.start_date), date(p.end_date)).months >= 2
AND duration.between(date(p.start_date), date(p.end_date)).months <= 4
// Note: some companies (e.g., NKE) have multi-axis facts like product×geography.
// The axis-grouped inventory ensures $member_qnames are from ONE axis,
// so cross-axis facts are only returned when the requested member appears
// in a multi-dimensional breakdown. These are valid sub-totals.
// If the agent needs strictly 1D data, add: AND count { (f)-[:FACT_MEMBER]->() } = 1
// Per-fact amendment overlay + dimensionality preference:
// - Fewer FACT_MEMBER rels = more consolidated (1D total > 2D sub-total)
// - Newest filing wins (amendment over original)
// - Highest decimals wins (more precise)
WITH r, r.periodOfReport AS quarter, datetime(r.created) AS filed,
m.qname AS member, m.label AS member_label,
f.value AS value, toInteger(f.decimals) AS dec,
count { (f)-[:FACT_MEMBER]->() } AS member_count
ORDER BY quarter DESC, member, member_count ASC, filed DESC, dec DESC
WITH quarter, member, collect({
available_at: filed,
available_at_source: 'edgar_accepted',
label: member_label,
value: value,
mc: member_count
})[0] AS best
// Wrap in PIT envelope format (data[] + gaps[])
WITH collect({
available_at: toString(best.available_at),
available_at_source: best.available_at_source,
quarter: quarter,
member: member,
member_label: best.label,
value: best.value,
dimensionality: best.mc
}) AS items
RETURN items AS data, [] AS gaps
Parameters:
$ticker (string)$concept_qname (string from segment_inventory, e.g., 'us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax')$member_qnames (list of strings from ONE axis in segment_inventory — never mix product + geo)$num_quarters (int, typically 4-8)$pit (ISO8601 or null for live — matches agent PIT contract, passed as pit in params dict)Design notes:
created wins per quarter+member. If amendment has the fact, it wins. If only original has it, original comes through. This handles partial amendments correctly.member_count ASC in the ORDER BY ensures 1D facts (totals) are preferred over 2D+ cross-dimensional facts (sub-totals). If SalesCloudMember appears in both a 1-member fact ($2B total) and a 3-member fact (SalesCloud×Americas = $1.5B), the 1-member fact wins. For companies like NKE where geographic segments are ALWAYS 3+ members, those still come through (no 1D alternative exists). The dimensionality column in the output lets the agent verify.period_end within 7 days of periodOfReport (excludes prior-year comparatives).MATCH (f:Fact)-[:IN_CONTEXT]->(ctx:Context)-[:HAS_PERIOD]->(p:Period)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE con.qname = $concept_qname
RETURN con.label, f.value, p.period_type, p.start_date, p.end_date
MATCH (f:Fact)-[:IN_CONTEXT]->(ctx:Context)-[:HAS_PERIOD]->(p:Period)
WHERE p.end_date = $period_end_date
AND p.period_type = 'duration'
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE f.is_numeric = '1'
RETURN con.qname, con.label, toFloat(f.value) AS value
LIMIT 50
MATCH (f:Fact)-[:FACT_DIMENSION]->(dim:Dimension)
MATCH (f)-[:FACT_MEMBER]->(m:Member)
MATCH (f)-[:HAS_CONCEPT]->(con:Concept)
WHERE f.id = $fact_id
RETURN con.label, dim.label AS dimension, m.label AS member, f.value
MATCH (f:Fact)-[:HAS_CONCEPT]->(con:Concept {qname: $concept_qname})
MATCH (f)-[:FACT_DIMENSION]->(dim:Dimension)
RETURN DISTINCT dim.qname, dim.label
CALL db.index.fulltext.queryNodes('concept_ft', $query)
YIELD node, score
RETURN node.qname, node.label, score
ORDER BY score DESC
LIMIT 20
CALL db.index.fulltext.queryNodes('fact_textblock_ft', $query)
YIELD node, score
RETURN node.qname, substring(node.value, 0, 300), score
ORDER BY score DESC
LIMIT 10
us-gaap:Revenuesus-gaap:RevenueFromContractWithCustomerExcludingAssessedTaxus-gaap:CostOfGoodsAndServicesSoldus-gaap:GrossProfitus-gaap:OperatingIncomeLossus-gaap:NetIncomeLossus-gaap:EarningsPerShareBasicus-gaap:EarningsPerShareDilutedus-gaap:Assetsus-gaap:Liabilitiesus-gaap:StockholdersEquityus-gaap:CashAndCashEquivalentsAtCarryingValueus-gaap:AccountsReceivableNetCurrentus-gaap:InventoryNetus-gaap:LongTermDebtus-gaap:NetCashProvidedByUsedInOperatingActivitiesus-gaap:NetCashProvidedByUsedInInvestingActivitiesus-gaap:NetCashProvidedByUsedInFinancingActivitiesus-gaap:PaymentsOfDividendsus-gaap:PaymentsForRepurchaseOfCommonStockMATCH (r:Report) WHERE r.xbrl_status IS NOT NULL
RETURN r.xbrl_status, COUNT(r) as count ORDER BY count DESC
MATCH (p:Period) RETURN p.period_type, COUNT(p) as count ORDER BY count DESC
MATCH (f:Fact) RETURN f.is_numeric, COUNT(f) as count ORDER BY count DESC
MATCH (f:Fact)-[:HAS_CONCEPT]->(c:Concept)
RETURN c.label, COUNT(f) as fact_count ORDER BY fact_count DESC LIMIT 20
MATCH (ctx:Context)
RETURN COUNT(ctx) as total_contexts,
COUNT(ctx.period_u_id) as has_period,
COUNT(ctx.member_u_ids) as has_members
MATCH (d:Dimension) OPTIONAL MATCH (d)-[:HAS_DOMAIN]->(dom)
RETURN d.name, d.label, COUNT(dom) as domain_count
ORDER BY domain_count DESC LIMIT 20
MATCH (r:Report)-[:HAS_XBRL]->(x:XBRLNode)
RETURN r.formType, COUNT(DISTINCT r) as report_count ORDER BY report_count DESC
MATCH (c:Company)<-[pf:PRIMARY_FILER]-(r:Report)-[:HAS_XBRL]->(x:XBRLNode)
WHERE r.formType = '10-K' AND datetime(r.created) > datetime() - duration('P180D')
RETURN c.ticker, r.created, r.accessionNo, pf.daily_stock as filing_day_return
ORDER BY r.created DESC LIMIT 20
MATCH (x:XBRLNode) RETURN COUNT(x) as total_xbrl_nodes
MATCH (c:Company)<-[:PRIMARY_FILER]-(r:Report)
WHERE r.xbrl_status = 'COMPLETED' AND datetime(r.created) > datetime() - duration('P90D')
RETURN c.ticker, r.formType, r.xbrl_status, r.created
ORDER BY r.created DESC LIMIT 20
MATCH (f:Fact) WHERE f.value IS NOT NULL AND f.value <> '0'
RETURN f.qname, f.value, f.is_numeric LIMIT 20
MATCH (f:Fact) WHERE f.is_numeric = '1' AND f.value IS NOT NULL AND f.value <> '0'
RETURN f.qname, f.value, f.decimals LIMIT 20
MATCH (p:Period)
RETURN p.period_type, COUNT(p) as count
ORDER BY count DESC
MATCH (u:Unit)
RETURN u.name, u.unit_reference, COUNT(u) as usage_count
ORDER BY usage_count DESC LIMIT 20
MATCH (c:Concept) WHERE c.qname STARTS WITH 'us-gaap:'
RETURN DISTINCT c.qname, c.label ORDER BY c.qname LIMIT 30
MATCH (r:Report)-[:HAS_XBRL]->(x:XBRLNode)
WHERE r.formType IN ['10-K', '10-Q']
RETURN r.formType, r.accessionNo, x.id, x.cik LIMIT 20
Queries for PIT (Point-in-Time) mode. All use <= $pit on parent Report (boundary-inclusive) and return the standard envelope format. Pass pit in the params dict alongside other Cypher parameters.
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K', '10-Q', '10-K/A', '10-Q/A']
AND r.created <= $pit
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)-[:HAS_CONCEPT]->(con:Concept)
MATCH (f)-[:IN_CONTEXT]->(:Context)
WHERE f.is_numeric = '1'
AND NOT EXISTS { (f)-[:FACT_MEMBER]->() }
OPTIONAL MATCH (f)-[:HAS_PERIOD]->(p:Period)
WITH r, f, con, p ORDER BY r.created DESC, con.qname
WITH collect({
available_at: r.created,
available_at_source: 'edgar_accepted',
accessionNo: r.accessionNo,
formType: r.formType,
concept_qname: con.qname,
concept_label: con.label,
value: f.value,
period_start: p.start_date,
period_end: p.end_date,
period_type: p.period_type
}) AS items
RETURN items AS data, [] AS gaps
MATCH (r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
WHERE r.formType IN ['10-K', '10-Q', '10-K/A', '10-Q/A']
AND r.created <= $pit
MATCH (r)-[:HAS_XBRL]->(x:XBRLNode)<-[:REPORTS]-(f:Fact)-[:HAS_CONCEPT]->(con:Concept)
MATCH (f)-[:IN_CONTEXT]->(:Context)
WHERE con.qname = $concept_qname
AND f.is_numeric = '1'
AND NOT EXISTS { (f)-[:FACT_MEMBER]->() }
OPTIONAL MATCH (f)-[:HAS_PERIOD]->(p:Period)
WITH r, f, con, p ORDER BY r.created DESC
WITH collect({
available_at: r.created,
available_at_source: 'edgar_accepted',
accessionNo: r.accessionNo,
formType: r.formType,
concept_qname: con.qname,
concept_label: con.label,
value: f.value,
period_start: p.start_date,
period_end: p.end_date,
period_type: p.period_type
}) AS items
RETURN items AS data, [] AS gaps
CALL db.index.fulltext.queryNodes('concept_ft', $query)
YIELD node, score
MATCH (f:Fact)-[:HAS_CONCEPT]->(node)
MATCH (f)-[:REPORTS]->(x:XBRLNode)<-[:HAS_XBRL]-(r:Report)-[:PRIMARY_FILER]->(c:Company {ticker: $ticker})
MATCH (f)-[:IN_CONTEXT]->(:Context)
WHERE r.formType IN ['10-K', '10-Q', '10-K/A', '10-Q/A']
AND r.created <= $pit
OPTIONAL MATCH (f)-[:HAS_PERIOD]->(p:Period)
WITH r, f, node, p, score ORDER BY score DESC LIMIT 20
WITH collect({
available_at: r.created,
available_at_source: 'edgar_accepted',
accessionNo: r.accessionNo,
formType: r.formType,
concept_qname: node.qname,
concept_label: node.label,
value: f.value,
period_start: p.start_date,
period_end: p.end_date,
period_type: p.period_type,
ft_score: score
}) AS items
RETURN items AS data, [] AS gaps
r.created <= $pit (XBRL has no own timestamp)available_at: r.created and available_at_source: 'edgar_accepted'WHERE r.formType IN ['10-K','10-Q','10-K/A','10-Q/A']collect({...}) to produce data[] arrayRETURN items AS data, [] AS gapspit in the params dict alongside other Cypher parameters{"data":[],"gaps":[]} passes the gatetoFloat() when is_numeric='1'.IN_CONTEXT relationship; filter with MATCH (f)-[:IN_CONTEXT]->(:Context).period_type='instant' (2,776 rows).concept_ft (label/qname), fact_textblock_ft (value/qname), abstract_ft (label).| Date | Gap | Affected | Mitigation |
|---|---|---|---|
| 2026-01-11 | Revenue values comma-formatted | HRL Revenue facts | Use f.value as string; toFloat() fails on "2,898,810,000" |
Version 1.2 | 2026-02-15 | Fixed PIT queries: corrected graph path (Fact-[:REPORTS]->XBRLNode, Fact-[:HAS_CONCEPT]->Concept), added Period joins